Compare commits
14 Commits
staging
...
feature/se
Author | SHA1 | Date |
---|---|---|
Connor Rigby | 3886304c54 | |
Connor Rigby | bd51878b41 | |
connor rigby | 3f06afdccb | |
connor rigby | f23eb81616 | |
Connor Rigby | 503e62507e | |
Connor Rigby | e79e1e4d05 | |
Connor Rigby | 6704a40c5a | |
Connor Rigby | fd7510fc94 | |
connor rigby | 10f3fddb9a | |
connor rigby | 9b9091d5e2 | |
Connor Rigby | 1a1bcb49bc | |
Connor Rigby | 12049e04ba | |
Connor Rigby | a76f67347a | |
Connor Rigby | d34ab2abb2 |
|
@ -17,6 +17,11 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
|||
%AST{ast | kind: :rpc_request, args: %{label: label}, body: []}
|
||||
end
|
||||
|
||||
def execute(%AST{} = ast, sequence_id) do
|
||||
ast
|
||||
|> add_body_node(new(:execute, %{sequence_id: sequence_id}))
|
||||
end
|
||||
|
||||
def read_pin(%AST{} = ast, pin_number, pin_mode) do
|
||||
ast
|
||||
|> add_body_node(new(:read_pin, %{pin_number: pin_number, pin_mode: pin_mode}))
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule FarmbotCore do
|
|||
def init([]) do
|
||||
|
||||
children = [
|
||||
FarmbotCore.DepTracker,
|
||||
FarmbotCore.Leds,
|
||||
FarmbotCore.EctoMigrator,
|
||||
FarmbotCore.BotState.Supervisor,
|
||||
|
|
|
@ -42,6 +42,7 @@ defmodule FarmbotCore.Asset.FbosConfig do
|
|||
field(:sequence_body_log, :boolean)
|
||||
field(:sequence_complete_log, :boolean)
|
||||
field(:sequence_init_log, :boolean)
|
||||
field(:boot_sequence_id, :id)
|
||||
|
||||
# private
|
||||
field(:monitor, :boolean, default: true)
|
||||
|
@ -65,7 +66,8 @@ defmodule FarmbotCore.Asset.FbosConfig do
|
|||
update_channel: fbos_config.update_channel,
|
||||
sequence_body_log: fbos_config.sequence_body_log,
|
||||
sequence_complete_log: fbos_config.sequence_complete_log,
|
||||
sequence_init_log: fbos_config.sequence_init_log
|
||||
sequence_init_log: fbos_config.sequence_init_log,
|
||||
boot_sequence_id: fbos_config.boot_sequence_id
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -88,6 +90,7 @@ defmodule FarmbotCore.Asset.FbosConfig do
|
|||
:sequence_body_log,
|
||||
:sequence_complete_log,
|
||||
:sequence_init_log,
|
||||
:boot_sequence_id,
|
||||
:monitor,
|
||||
:created_at,
|
||||
:updated_at
|
||||
|
|
|
@ -7,8 +7,15 @@ defmodule FarmbotCore.Asset.Query do
|
|||
|
||||
@callback auto_sync?() :: boolean()
|
||||
|
||||
@callback first_sync?() :: boolean()
|
||||
|
||||
@doc "Returns the configuration value for auto_sync"
|
||||
def auto_sync?() do
|
||||
Asset.fbos_config().auto_sync
|
||||
end
|
||||
|
||||
@doc "Checks if initial syncing is still required"
|
||||
def first_sync?() do
|
||||
is_nil(Asset.fbos_config().id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,20 @@ defmodule FarmbotCore.AssetSupervisor do
|
|||
use Supervisor
|
||||
alias FarmbotCore.{Asset.Repo, AssetWorker}
|
||||
|
||||
def get_state(%{} = asset) do
|
||||
case whereis_child(asset) do
|
||||
{_id, pid, _, _} -> :sys.get_state(pid)
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def get_pid(%{} = asset) do
|
||||
case whereis_child(asset) do
|
||||
{_id, pid, _, _} -> pid
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc "List all children for an asset"
|
||||
def list_children(kind) do
|
||||
name = Module.concat(__MODULE__, kind)
|
||||
|
|
|
@ -2,7 +2,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
use GenServer
|
||||
require FarmbotCore.Logger
|
||||
|
||||
alias FarmbotCore.{Asset.Repo, BotState, JSON}
|
||||
alias FarmbotCore.{Asset.Repo, BotState, DepTracker, JSON}
|
||||
alias FarmbotCore.Asset.FarmwareInstallation, as: FWI
|
||||
alias FarmbotCore.Asset.FarmwareInstallation.Manifest
|
||||
|
||||
|
@ -21,7 +21,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
end
|
||||
|
||||
def init(fwi) do
|
||||
{:ok, %{fwi: fwi, backoff: 0}, 0}
|
||||
:ok = DepTracker.register_asset(fwi, :init)
|
||||
send self(), :timeout
|
||||
{:ok, %{fwi: fwi, backoff: 0}}
|
||||
end
|
||||
|
||||
def handle_cast(:update, state) do
|
||||
|
@ -39,6 +41,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
:ok <- install_zip(updated, zip_binary),
|
||||
:ok <- install_farmware_tools(updated),
|
||||
:ok <- write_manifest(updated) do
|
||||
:ok = DepTracker.register_asset(fwi, :complete)
|
||||
FarmbotCore.Logger.success(1, "Installed Farmware: #{updated.manifest.package}")
|
||||
# TODO(Connor) -> No reason to keep this process alive?
|
||||
BotState.report_farmware_installed(updated.manifest.package, Manifest.view(updated.manifest))
|
||||
|
@ -47,8 +50,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
error ->
|
||||
backoff = state.backoff + @back_off_time_ms
|
||||
timeout = @error_retry_time_ms + backoff
|
||||
Process.send_after(self(), :timeout, timeout)
|
||||
error_log(fwi, "Failed to download Farmware manifest. Trying again in #{timeout}ms #{inspect(error)}")
|
||||
{:noreply, %{state | backoff: backoff}, timeout}
|
||||
{:noreply, %{state | backoff: backoff}}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -76,14 +80,17 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
updated =
|
||||
FWI.changeset(fwi, %{manifest: nil, updated_at: fwi.updated_at})
|
||||
|> Repo.update!()
|
||||
|
||||
{:noreply, %{state | fwi: updated}, 0}
|
||||
send self(), :timeout
|
||||
{:noreply, %{state | fwi: updated}}
|
||||
|
||||
error ->
|
||||
:ok = DepTracker.register_asset(fwi, :complete)
|
||||
BotState.report_farmware_installed(fwi.manifest.package, Manifest.view(fwi.manifest))
|
||||
backoff = state.backoff + @back_off_time_ms
|
||||
timeout = @error_retry_time_ms + backoff
|
||||
Process.send_after(self(), :timeout, timeout)
|
||||
error_log(fwi, "failed to check for updates. Trying again in #{timeout}ms #{inspect(error)}")
|
||||
{:noreply, %{state | backoff: backoff}, timeout}
|
||||
{:noreply, %{state | backoff: backoff}}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -92,6 +99,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
# Installed is newer than remote.
|
||||
:gt ->
|
||||
success_log(updated, "up to date.")
|
||||
:ok = DepTracker.register_asset(updated, :complete)
|
||||
BotState.report_farmware_installed(updated.manifest.package, Manifest.view(updated.manifest))
|
||||
|
||||
{:noreply, %{state | fwi: updated}}
|
||||
|
@ -99,6 +107,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
# No difference between installed and remote.
|
||||
:eq ->
|
||||
success_log(updated, "up to date.")
|
||||
:ok = DepTracker.register_asset(updated, :complete)
|
||||
BotState.report_farmware_installed(updated.manifest.package, Manifest.view(updated.manifest))
|
||||
|
||||
{:noreply, %{state | fwi: updated}}
|
||||
|
@ -111,6 +120,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
:ok <- install_zip(updated, zip_binary),
|
||||
:ok <- install_farmware_tools(updated),
|
||||
:ok <- write_manifest(updated) do
|
||||
:ok = DepTracker.register_asset(updated, :complete)
|
||||
BotState.report_farmware_installed(updated.manifest.package, Manifest.view(updated.manifest))
|
||||
{:noreply, %{state | fwi: updated, backoff: 0}}
|
||||
|
||||
|
@ -118,8 +128,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
er ->
|
||||
backoff = state.backoff + @back_off_time_ms
|
||||
timeout = @error_retry_time_ms + backoff
|
||||
Process.send_after(self(), :timeout, timeout)
|
||||
error_log(updated, "update failed. Trying again in #{timeout}ms #{inspect(er)}")
|
||||
{:noreply, %{state | fwi: updated, backoff: backoff}, timeout}
|
||||
{:noreply, %{state | fwi: updated, backoff: backoff}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -246,7 +257,16 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmwareInstallation do
|
|||
end
|
||||
|
||||
defp httpc_options, do: [body_format: :binary]
|
||||
defp httpc_headers, do: [{'user-agent', 'farmbot-os'}]
|
||||
|
||||
case System.get_env("GITHUB_TOKEN") do
|
||||
nil ->
|
||||
defp httpc_headers, do: [{'user-agent', 'farmbot-farmware-installer'}]
|
||||
|
||||
token when is_binary(token) ->
|
||||
@token token
|
||||
require Logger; Logger.info "using github token: #{@token}"
|
||||
defp httpc_headers, do: [{'user-agent', 'farmbot-farmware-installer'}, {'Authorization', 'token #{@token}'}]
|
||||
end
|
||||
|
||||
def install_dir(%FWI{} = fwi) do
|
||||
install_dir(fwi.manifest)
|
||||
|
|
|
@ -8,7 +8,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias FarmbotCore.{Asset.FbosConfig, BotState, Config}
|
||||
alias FarmbotCore.{Asset, Asset.FbosConfig, BotState, Config, DepTracker}
|
||||
import FarmbotFirmware.PackageUtils, only: [package_to_string: 1]
|
||||
|
||||
@firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold]
|
||||
|
@ -35,6 +35,14 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
|
||||
@impl GenServer
|
||||
def init(%FbosConfig{} = fbos_config) do
|
||||
:ok = DepTracker.register_asset(fbos_config, :init)
|
||||
%{
|
||||
informational_settings: %{
|
||||
idle: fw_idle,
|
||||
firmware_version: fw_version,
|
||||
firmware_configured: fw_configured
|
||||
}
|
||||
} = BotState.subscribe()
|
||||
if Config.get_config_value(:bool, "settings", "firmware_needs_flash") do
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||
end
|
||||
|
@ -44,20 +52,26 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
firmware_flash_attempts: 0,
|
||||
firmware_flash_attempt_threshold: @firmware_flash_attempt_threshold,
|
||||
firmware_flash_timeout: @firmware_flash_timeout,
|
||||
firmware_flash_in_progress: false
|
||||
firmware_flash_in_progress: false,
|
||||
firmware_flash_ref: nil,
|
||||
firmware_idle: fw_idle,
|
||||
firmware_version: fw_version,
|
||||
firmware_configured: fw_configured,
|
||||
}
|
||||
{:ok, state, 0}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_info({:step_complete, _, :ok}, state) do
|
||||
def handle_info({:step_complete, ref, :ok}, %{firmware_flash_ref: ref} = state) do
|
||||
DepTracker.register_asset(state.fbos_config, :idle)
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_flash", false)
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", true)
|
||||
{:noreply, %{state | firmware_flash_in_progress: false}}
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, _, {:error, reason}}, %{firmware_flash_attempts: tries, firmware_flash_attempt_threshold: thresh} = state)
|
||||
def handle_info({:step_complete, ref, {:error, reason}}, %{firmware_flash_ref: ref, firmware_flash_attempts: tries, firmware_flash_attempt_threshold: thresh} = state)
|
||||
when tries >= thresh do
|
||||
DepTracker.register_asset(state.fbos_config, :idle)
|
||||
FarmbotCore.Logger.error 1, """
|
||||
Failed flashing firmware: #{reason}
|
||||
Tried #{tries} times. Not retrying
|
||||
|
@ -67,7 +81,8 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
{:noreply, %{state | firmware_flash_attempts: 0, firmware_flash_in_progress: false}}
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, _, {:error, reason}}, %{fbos_config: %FbosConfig{} = fbos_config} = state) do
|
||||
def handle_info({:step_complete, ref, {:error, reason}}, %{firmware_flash_ref: ref, fbos_config: %FbosConfig{} = fbos_config} = state) do
|
||||
DepTracker.register_asset(fbos_config, :flash_firmware)
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_flash", true)
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||
|
||||
|
@ -97,10 +112,12 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
end
|
||||
|
||||
def handle_info({:maybe_flash_firmware, old_fbos_config}, %{fbos_config: %FbosConfig{} = fbos_config} = state) do
|
||||
unless state.firmware_flash_in_progress do
|
||||
_ = maybe_flash_firmware(state, fbos_config.firmware_hardware, old_fbos_config.firmware_hardware)
|
||||
if state.firmware_flash_in_progress do
|
||||
{:noreply, state}
|
||||
else
|
||||
ref = maybe_flash_firmware(state, fbos_config.firmware_hardware, old_fbos_config.firmware_hardware)
|
||||
{:noreply, %{state | firmware_flash_ref: ref}}
|
||||
end
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:maybe_start_io_log_timer, old_fbos_config}, %{fbos_config: fbos_config, firmware_io_timer: nil} = state) do
|
||||
|
@ -138,6 +155,27 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
{:noreply, %{state | fbos_config: new_fbos_config, firmware_io_timer: nil}}
|
||||
end
|
||||
|
||||
def handle_info({BotState, %{changes: %{informational_settings: %{changes: %{idle: idle}}}}}, state) do
|
||||
{:noreply, %{state | firmware_idle: idle}}
|
||||
end
|
||||
|
||||
def handle_info({BotState, %{changes: %{informational_settings: %{changes: %{firmware_version: fw_version}}}}}, state) do
|
||||
{:noreply, %{state | firmware_version: fw_version}}
|
||||
end
|
||||
|
||||
def handle_info({BotState, %{changes: %{informational_settings: %{changes: %{firmware_configured: fw_configured}}}}}, state) do
|
||||
# this should really be fixed upstream not to dispatch if version is none.
|
||||
if state.firmware_version == "none" do
|
||||
{:noreply, state}
|
||||
else
|
||||
{:noreply, %{state | firmware_configured: fw_configured}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({BotState, _}, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_cast({:new_data, new_fbos_config}, %{fbos_config: %FbosConfig{} = old_fbos_config} = state) do
|
||||
_ = set_config_to_state(new_fbos_config, old_fbos_config)
|
||||
|
@ -149,33 +187,39 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
def maybe_flash_firmware(_state, "none", _old_hardware) do
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_flash", false)
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", true)
|
||||
:ok
|
||||
nil
|
||||
end
|
||||
|
||||
def maybe_flash_firmware(_state, nil, _old_hardware) do
|
||||
FarmbotCore.Logger.warn 1, "Firmware hardware unset. Not flashing"
|
||||
:ok
|
||||
nil
|
||||
end
|
||||
|
||||
def maybe_flash_firmware(_state, new_hardware, old_hardware) do
|
||||
def maybe_flash_firmware(state, new_hardware, old_hardware) do
|
||||
force? = Config.get_config_value(:bool, "settings", "firmware_needs_flash")
|
||||
ref = make_ref()
|
||||
cond do
|
||||
force? ->
|
||||
DepTracker.register_asset(state.fbos_config, :firmware_flash)
|
||||
FarmbotCore.Logger.warn 1, "Firmware hardware forced flash"
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_flash", false)
|
||||
new_hardware
|
||||
|> fbos_config_to_flash_firmware_rpc()
|
||||
|> FarmbotCeleryScript.execute(make_ref())
|
||||
|> FarmbotCeleryScript.execute(ref)
|
||||
ref
|
||||
|
||||
new_hardware != old_hardware ->
|
||||
DepTracker.register_asset(state.fbos_config, :firmware_flash)
|
||||
FarmbotCore.Logger.warn 1, "Firmware hardware change from #{package_to_string(old_hardware)} to #{package_to_string(new_hardware)}"
|
||||
new_hardware
|
||||
|> fbos_config_to_flash_firmware_rpc()
|
||||
|> FarmbotCeleryScript.execute(make_ref())
|
||||
|> FarmbotCeleryScript.execute(ref)
|
||||
ref
|
||||
|
||||
true ->
|
||||
DepTracker.register_asset(state.fbos_config, :idle)
|
||||
# Config.update_config_value(:bool, "settings", "firmware_needs_open", true)
|
||||
:ok
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -193,7 +237,8 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
:sequence_body_log,
|
||||
:sequence_complete_log,
|
||||
:sequence_init_log,
|
||||
:update_channel
|
||||
:update_channel,
|
||||
:boot_sequence_id
|
||||
]
|
||||
new_interesting_fbos_config = Map.take(new_fbos_config, interesting_params) |> MapSet.new()
|
||||
old_interesting_fbos_config = Map.take(old_fbos_config, interesting_params) |> MapSet.new()
|
||||
|
@ -241,6 +286,17 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
{:sequence_init_log, bool} ->
|
||||
FarmbotCore.Logger.success 1, "Set sequence init log messages to #{bool}"
|
||||
|
||||
{:boot_sequence_id, nil} ->
|
||||
FarmbotCore.Logger.success 1, "Set bootup sequence to none"
|
||||
|
||||
{:boot_sequence_id, id} ->
|
||||
case Asset.get_sequence(id) do
|
||||
%{name: name} ->
|
||||
FarmbotCore.Logger.success 1, "Set bootup sequence to #{name}"
|
||||
_ ->
|
||||
FarmbotCore.Logger.success 1, "Set bootup sequence"
|
||||
end
|
||||
|
||||
{param, value} ->
|
||||
FarmbotCore.Logger.success 1, "Set #{param} to #{value}"
|
||||
end)
|
||||
|
@ -270,7 +326,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
|
||||
def fbos_config_to_flash_firmware_rpc(firmware_hardware) do
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("FbosConfig")
|
||||
|> AST.Factory.rpc_request("fbos_config.flash_firmware")
|
||||
|> AST.Factory.flash_firmware(firmware_hardware)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
||||
use GenServer
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
|
||||
alias FarmbotCore.{Asset.Peripheral, BotState}
|
||||
alias FarmbotCore.{Asset.FbosConfig, Asset.Peripheral, BotState, DepTracker}
|
||||
alias FarmbotCeleryScript.AST
|
||||
|
||||
@retry_ms 1_000
|
||||
|
||||
@unacceptable_fbos_config_statuses [
|
||||
nil,
|
||||
:init,
|
||||
:firmware_flash,
|
||||
:bootup_sequence,
|
||||
]
|
||||
|
||||
@impl true
|
||||
def preload(%Peripheral{}), do: []
|
||||
|
||||
|
@ -20,40 +28,81 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
|
||||
@impl true
|
||||
def init(peripheral) do
|
||||
%{informational_settings: %{idle: idle, firmware_version: fw_version}} = BotState.subscribe()
|
||||
state = %{peripheral: peripheral, errors: 0, fw_idle: idle || false, fw_version: fw_version}
|
||||
send self(), :timeout
|
||||
:ok = DepTracker.register_asset(peripheral, :init)
|
||||
%{
|
||||
informational_settings: %{
|
||||
idle: idle,
|
||||
firmware_version: fw_version,
|
||||
firmware_configured: fw_configured
|
||||
}
|
||||
} = BotState.subscribe()
|
||||
state = %{
|
||||
peripheral: peripheral,
|
||||
errors: 0,
|
||||
fw_idle: idle || false,
|
||||
fw_version: fw_version,
|
||||
fw_configured: fw_configured || false,
|
||||
fbos_config_status: nil
|
||||
}
|
||||
:ok = DepTracker.subscribe_asset(FbosConfig)
|
||||
send self(), :try_read_peripheral
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:timeout, %{fw_version: nil} = state) do
|
||||
# Logger.debug("Not reading peripheral. Firmware not started.")
|
||||
Process.send_after(self(), :timeout, @retry_ms)
|
||||
def handle_info({DepTracker, {FbosConfig, _}, _old, status}, state) do
|
||||
{:noreply, %{state | fbos_config_status: status}}
|
||||
end
|
||||
|
||||
def handle_info(:try_read_peripheral, %{fbos_config_status: fbos_config_status} = state)
|
||||
when fbos_config_status in @unacceptable_fbos_config_statuses do
|
||||
# Logger.debug("Not reading peripheral. fbos_config not in acceptable state: #{fbos_config_status}")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{fw_version: "8.0.0.S"} = state) do
|
||||
def handle_info(:try_read_peripheral, %{fbos_config_status: :bootup_sequence} = state) do
|
||||
# Logger.debug("Not reading peripheral. Bootup sequence not complete")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{fw_idle: false} = state) do
|
||||
def handle_info(:try_read_peripheral, %{fw_version: nil} = state) do
|
||||
# Logger.debug("Not reading peripheral. Firmware not booted.")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:try_read_peripheral, %{fw_version: "none"} = state) do
|
||||
# Logger.debug("Not reading peripheral. Firmware not booted.")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:try_read_peripheral, %{fw_configured: false} = state) do
|
||||
# Logger.debug("Not reading peripheral. Firmware not configured.")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:try_read_peripheral, %{fw_idle: false} = state) do
|
||||
# Logger.debug("Not reading peripheral. Firmware not idle.")
|
||||
Process.send_after(self(), :timeout, @retry_ms)
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{peripheral: peripheral, errors: errors} = state) do
|
||||
def handle_info(:try_read_peripheral, %{peripheral: peripheral, errors: errors} = state) do
|
||||
Logger.debug("Read peripheral: #{peripheral.label}")
|
||||
rpc = peripheral_to_rpc(peripheral)
|
||||
case FarmbotCeleryScript.execute(rpc, make_ref()) do
|
||||
:ok ->
|
||||
:ok = DepTracker.register_asset(peripheral, :complete)
|
||||
Logger.debug("Read peripheral: #{peripheral.label} ok")
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} when errors < 5 ->
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors}")
|
||||
Process.send_after(self(), :timeout, @retry_ms)
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors} status=#{state.fbos_config_status}")
|
||||
Process.send_after(self(), :try_read_peripheral, @retry_ms)
|
||||
{:noreply, %{state | errors: state.errors + 1}}
|
||||
|
||||
{:error, reason} when errors == 5 ->
|
||||
|
@ -70,6 +119,15 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
{:noreply, %{state | fw_version: fw_version}}
|
||||
end
|
||||
|
||||
def handle_info({BotState, %{changes: %{informational_settings: %{changes: %{firmware_configured: fw_configured}}}}}, state) do
|
||||
# this should really be fixed upstream not to dispatch if version is none.
|
||||
if state.fw_version == "none" do
|
||||
{:noreply, state}
|
||||
else
|
||||
{:noreply, %{state | fw_configured: fw_configured}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({BotState, _}, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
@ -80,7 +138,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
|
||||
def peripheral_to_rpc(peripheral) do
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request(peripheral.local_id)
|
||||
|> AST.Factory.rpc_request("peripheral." <> peripheral.local_id)
|
||||
|> AST.Factory.set_pin_io_mode(peripheral.pin, "output")
|
||||
|> AST.Factory.read_pin(peripheral.pin, peripheral.mode)
|
||||
end
|
||||
|
|
|
@ -72,6 +72,10 @@ defmodule FarmbotCore.BotState do
|
|||
def set_firmware_version(bot_state_server \\ __MODULE__, version) do
|
||||
GenServer.call(bot_state_server, {:set_firmware_version, version})
|
||||
end
|
||||
|
||||
def set_firmware_configured(bot_state_server \\ __MODULE__, configured) do
|
||||
GenServer.call(bot_state_server, {:set_firmware_configured, configured})
|
||||
end
|
||||
|
||||
@doc "Sets configuration.arduino_hardware"
|
||||
def set_firmware_hardware(bot_state_server \\ __MODULE__, hardware) do
|
||||
|
@ -306,6 +310,17 @@ defmodule FarmbotCore.BotState do
|
|||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:set_firmware_configured, configured}, _from, state) do
|
||||
change = %{informational_settings: %{firmware_configured: configured}}
|
||||
|
||||
{reply, state} =
|
||||
BotStateNG.changeset(state.tree, change)
|
||||
|> dispatch_and_apply(state)
|
||||
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
|
||||
def handle_call({:set_firmware_hardware, hardware}, _from, state) do
|
||||
change = %{configuration: %{firmware_hardware: hardware}}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule FarmbotCore.BotStateNG.InformationalSettings do
|
|||
field(:controller_uuid, :string)
|
||||
field(:controller_commit, :string, default: Project.commit())
|
||||
field(:firmware_version, :string)
|
||||
field(:firmware_configured, :boolean, default: false)
|
||||
field(:node_name, :string)
|
||||
field(:private_ip, :string)
|
||||
field(:soc_temp, :integer)
|
||||
|
@ -52,6 +53,7 @@ defmodule FarmbotCore.BotStateNG.InformationalSettings do
|
|||
commit: informational_settings.controller_commit,
|
||||
firmware_commit: informational_settings.firmware_commit,
|
||||
firmware_version: informational_settings.firmware_version,
|
||||
firmware_configured: informational_settings.firmware_configured,
|
||||
node_name: informational_settings.node_name,
|
||||
private_ip: informational_settings.private_ip,
|
||||
soc_temp: informational_settings.soc_temp,
|
||||
|
@ -84,6 +86,7 @@ defmodule FarmbotCore.BotStateNG.InformationalSettings do
|
|||
:controller_commit,
|
||||
:firmware_commit,
|
||||
:firmware_version,
|
||||
:firmware_configured,
|
||||
:node_name,
|
||||
:private_ip,
|
||||
:soc_temp,
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
defmodule FarmbotCore.DepTracker do
|
||||
@moduledoc """
|
||||
Subscribe to internal dependency and service status events.
|
||||
"""
|
||||
alias FarmbotCore.{DepTracker, DepTracker.Table}
|
||||
|
||||
@doc "Start a dep tracker instance"
|
||||
def start_link(options) do
|
||||
name = Keyword.get(options, :name, DepTracker)
|
||||
|
||||
unless !is_nil(name) and is_atom(name) do
|
||||
raise ArgumentError, "expected :name to be given and to be an atom, got: #{inspect(name)}"
|
||||
end
|
||||
DepTracker.Supervisor.start_link(name)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: Keyword.get(opts, :name, DepTracker),
|
||||
start: {DepTracker, :start_link, [opts]},
|
||||
type: :supervisor
|
||||
}
|
||||
end
|
||||
|
||||
@doc "register an asset in the tracker"
|
||||
def register_asset(table \\ DepTracker, %kind{local_id: local_id}, status) do
|
||||
Table.put(table, {{kind, local_id}, status})
|
||||
end
|
||||
|
||||
@doc "register a service in the tracker"
|
||||
def register_service(table \\ DepTracker, service_name, status) do
|
||||
Table.put(table, {service_name, status})
|
||||
end
|
||||
|
||||
@doc """
|
||||
subscribe to asset changes from the tracker
|
||||
messages are dispatched in the shape of
|
||||
|
||||
{table_name, {kind, local_id}, status}
|
||||
"""
|
||||
def subscribe_asset(table \\ DepTracker, kind) do
|
||||
:ok = do_subscribe(table, kind)
|
||||
initial = get_asset(table, kind)
|
||||
for {{kind, local_id}, status} <- initial do
|
||||
send self(), {table, {kind, local_id}, nil, status}
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc "get all current assets by kind"
|
||||
def get_asset(table \\ DepTracker, kind) do
|
||||
Table.get(table, {kind, :"$1"})
|
||||
end
|
||||
|
||||
@doc """
|
||||
subscribe to service changes from the tracker
|
||||
messages are dispatched in the shape of
|
||||
|
||||
{table_name, service_name, status}
|
||||
"""
|
||||
def subscribe_service(table \\ DepTracker, service_name) do
|
||||
:ok = do_subscribe(table, service_name)
|
||||
initial = get_service(table, service_name)
|
||||
for {^service_name, status} <- initial do
|
||||
send self(), {table, {service_name, nil, status}}
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc "get all current services by name"
|
||||
def get_service(table \\ DepTracker, service_name) do
|
||||
Table.get(table, service_name)
|
||||
end
|
||||
|
||||
defp do_subscribe(table, name) do
|
||||
registry = DepTracker.Supervisor.registry_name(table)
|
||||
{:ok, _} = Registry.register(registry, name, nil)
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
defmodule FarmbotCore.DepTracker.Logger do
|
||||
alias FarmbotCore.DepTracker
|
||||
require Logger
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
def child_spec(args) do
|
||||
%{
|
||||
id: name(args),
|
||||
start: {FarmbotCore.DepTracker.Logger, :start_link, [args]},
|
||||
}
|
||||
end
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: name(args)])
|
||||
end
|
||||
|
||||
defp name({table, [service: service_name]}) do
|
||||
Module.concat([__MODULE__, table, service_name])
|
||||
end
|
||||
|
||||
defp name({table, [asset: kind]}) do
|
||||
Module.concat([__MODULE__, table, kind])
|
||||
end
|
||||
|
||||
def init({table, [service: service_name]}) do
|
||||
:ok = DepTracker.subscribe_service(table, service_name)
|
||||
{:ok, %{service: service_name, table: table}}
|
||||
end
|
||||
|
||||
def init({table, [asset: kind]}) do
|
||||
:ok = DepTracker.subscribe_asset(table, kind)
|
||||
{:ok, %{asset: kind, table: table}}
|
||||
end
|
||||
|
||||
def handle_info({table, {kind, local_id}, old_status, new_status}, %{asset: kind, table: table} = state) do
|
||||
Logger.info """
|
||||
#{inspect(table)} asset status change:
|
||||
#{kind} local_id = #{local_id}
|
||||
#{kind} #{inspect(old_status)} => #{inspect(new_status)}
|
||||
"""
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({table, service, old_status, new_status}, %{service: service, table: table} = state) do
|
||||
Logger.info """
|
||||
#{inspect(table)} service status change:
|
||||
#{service} #{inspect(old_status)} => #{inspect(new_status)}
|
||||
"""
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
defmodule FarmbotCore.DepTracker.Supervisor do
|
||||
use Supervisor
|
||||
|
||||
alias FarmbotCore.DepTracker
|
||||
|
||||
@moduledoc false
|
||||
|
||||
def start_link(name) do
|
||||
Supervisor.start_link(__MODULE__, name)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(name) do
|
||||
registry_name = registry_name(name)
|
||||
|
||||
children = [
|
||||
{DepTracker.Table, {name, registry_name}},
|
||||
{Registry, [keys: :duplicate, name: registry_name]},
|
||||
# {DepTracker.Logger, {name, service: :firmware}},
|
||||
{DepTracker.Logger, {name, asset: FarmbotCore.Asset.FbosConfig}},
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def registry_name(name) do
|
||||
Module.concat(DepTracker.Registry, name)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
defmodule FarmbotCore.DepTracker.Table do
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
def start_link({table, _registry_name} = args) do
|
||||
GenServer.start_link(__MODULE__, args, name: table)
|
||||
end
|
||||
|
||||
@doc "put data"
|
||||
def put(table, {identifier, status}) do
|
||||
GenServer.call(table, {:put, identifier, status})
|
||||
end
|
||||
|
||||
@doc "get data"
|
||||
def get(table, {kind, _} = identifier) do
|
||||
:ets.match(table, {identifier, :"$2"})
|
||||
|> Enum.map(fn
|
||||
[local_id, status] -> {{kind, local_id}, status}
|
||||
other -> raise("unknown data in ets table: #{table} data: #{inspect(other)}")
|
||||
end)
|
||||
end
|
||||
|
||||
def get(table, service_name) do
|
||||
:ets.match(table, {service_name, :"$2"})
|
||||
|> Enum.map(fn
|
||||
[status] -> {service_name, status}
|
||||
other -> raise("unknown data in ets table: #{table} data: #{inspect(other)}")
|
||||
end)
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def init({table, registry_name}) do
|
||||
^table = :ets.new(table, [:named_table, read_concurrency: true])
|
||||
|
||||
state = %{table: table, registry: registry_name}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl GenServer
|
||||
def handle_call({:put, identifier, status}, _from, state) do
|
||||
case :ets.lookup(state.table, identifier) do
|
||||
[{^identifier, ^status}] ->
|
||||
# No change, so no notifications
|
||||
:ok
|
||||
|
||||
[{^identifier, old_status}] ->
|
||||
:ets.insert(state.table, {identifier, status})
|
||||
dispatch(state, identifier, old_status, status)
|
||||
|
||||
[] ->
|
||||
:ets.insert(state.table, {identifier, status})
|
||||
dispatch(state, identifier, nil, status)
|
||||
end
|
||||
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
defp dispatch(state, identifier, old, new) do
|
||||
kind = case identifier do
|
||||
{kind, _} -> kind
|
||||
kind -> kind
|
||||
end
|
||||
Registry.dispatch(state.registry, kind, fn entries ->
|
||||
message = {state.table, identifier, old, new}
|
||||
for {pid, _} <- entries, do: send(pid, message)
|
||||
end)
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
use GenServer
|
||||
require FarmbotCore.Logger
|
||||
alias FarmbotFirmware.{UARTTransport, StubTransport}
|
||||
alias FarmbotCore.{Asset, Config}
|
||||
alias FarmbotCore.{Asset, Config, DepTracker}
|
||||
@attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:attempt_threshold]
|
||||
@attempt_threshold || Mix.raise """
|
||||
Firmware open attempt threshold not configured:
|
||||
|
@ -89,6 +89,7 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
:ok ->
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||
timer = Process.send_after(self(), :open, 5000)
|
||||
DepTracker.register_service(:firmware, :init)
|
||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||
_ ->
|
||||
FarmbotCore.Logger.debug 3, "Firmware failed to open"
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
@behaviour FarmbotFirmware.SideEffects
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
alias FarmbotCore.{Asset, BotState, FirmwareEstopTimer, Leds}
|
||||
alias FarmbotCore.{Asset, BotState, DepTracker, FirmwareEstopTimer, Leds}
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_position(x: x, y: y, z: z) do
|
||||
|
@ -111,6 +111,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_busy(busy) do
|
||||
:ok = BotState.set_firmware_busy(busy)
|
||||
DepTracker.register_service(:firmware, :busy)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
|
@ -118,6 +119,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
_ = FirmwareEstopTimer.cancel_timer()
|
||||
:ok = BotState.set_firmware_unlocked()
|
||||
:ok = BotState.set_firmware_idle(idle)
|
||||
DepTracker.register_service(:firmware, :idle)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
|
@ -125,6 +127,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
_ = FirmwareEstopTimer.start_timer()
|
||||
_ = Leds.yellow(:slow_blink)
|
||||
:ok = BotState.set_firmware_locked()
|
||||
DepTracker.register_service(:firmware, :locked)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
|
@ -163,6 +166,12 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
should_log? && FarmbotCore.Logger.debug(3, "Firmware debug message: " <> message)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_configuration_status(status) do
|
||||
:ok = BotState.set_firmware_configured(status)
|
||||
DepTracker.register_service(:firmware, :configured)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def load_params do
|
||||
conf = Asset.firmware_config()
|
||||
|
|
|
@ -51,7 +51,7 @@ defmodule FarmbotCore.MixProject do
|
|||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
extra_applications: [:logger, :inets],
|
||||
mod: {FarmbotCore, []}
|
||||
]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.AddBootSequenceIdToFbosConfig do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("fbos_configs") do
|
||||
add(:boot_sequence_id, :id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.ForceResyncFbosConfig do
|
||||
use Ecto.Migration
|
||||
alias FarmbotCore.Asset.{Repo, FbosConfig}
|
||||
|
||||
def change do
|
||||
if fbos_config = Repo.one(FbosConfig) do
|
||||
execute("UPDATE fbos_configs SET updated_at = \"1970-11-07 16:52:31.618000\"")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.ForceReinstallFarmware do
|
||||
use Ecto.Migration
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
Repo,
|
||||
FirstPartyFarmware,
|
||||
FarmwareInstallation
|
||||
}
|
||||
|
||||
# this migration is here because v8 was shipped with a configuration error
|
||||
# causing farmware to install to the temporary data partition on the
|
||||
# raspberry pi.
|
||||
def change do
|
||||
for %{manifest: %{}} = fwi <- Repo.all(FirstPartyFarmware),
|
||||
do: Repo.update!(FirstPartyFarmware.changeset(fwi, %{manifest: nil}))
|
||||
|
||||
for %{manifest: %{}} = fwi <- Repo.all(FarmwareInstallation),
|
||||
do: Repo.update!(FarmwareInstallation.changeset(fwi, %{manifest: nil}))
|
||||
end
|
||||
end
|
|
@ -79,9 +79,13 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
|
|||
|
||||
def handle_info(:preload, state) do
|
||||
_ = Leds.green(:really_fast_blink)
|
||||
# this must be called __before__ preloading.
|
||||
# if it's not, it will have been reset by the time the
|
||||
# preload completes
|
||||
first_sync? = Asset.Query.first_sync?()
|
||||
|
||||
with :ok <- Preloader.preload_all() do
|
||||
if Asset.Query.auto_sync?() do
|
||||
if Asset.Query.auto_sync?() || first_sync? do
|
||||
_ = Leds.green(:solid)
|
||||
BotState.set_sync_status("synced")
|
||||
else
|
||||
|
|
|
@ -66,7 +66,7 @@ defmodule FarmbotExt.API.EagerLoader do
|
|||
* a remote `id` field.
|
||||
"""
|
||||
def cache(%Changeset{data: %module{}} = changeset) do
|
||||
Logger.info("Caching #{inspect(changeset)}")
|
||||
# Logger.info("Caching #{inspect(changeset)}")
|
||||
id = Changeset.get_field(changeset, :id)
|
||||
updated_at = Changeset.get_field(changeset, :updated_at)
|
||||
id || change_error(changeset, "Can't cache a changeset with no :id attribute")
|
||||
|
|
|
@ -21,10 +21,19 @@ defmodule FarmbotExt.API.Preloader do
|
|||
actually sync all resources. If it is not, preload all resources.
|
||||
"""
|
||||
def preload_all() do
|
||||
# this must be called __before__ preloading.
|
||||
# if it's not, it will have been reset by the time the
|
||||
# preload completes
|
||||
first_sync? = Query.first_sync?()
|
||||
|
||||
if first_sync? do
|
||||
FarmbotCore.Logger.info(2, "Farmbot doing first sync")
|
||||
end
|
||||
|
||||
with {:ok, sync_changeset} <- API.get_changeset(Sync),
|
||||
sync_changeset <- Reconciler.sync_group(sync_changeset, SyncGroup.group_0()) do
|
||||
FarmbotCore.Logger.success(3, "Successfully preloaded resources.")
|
||||
maybe_auto_sync(sync_changeset, Query.auto_sync?())
|
||||
maybe_auto_sync(sync_changeset, Query.auto_sync?() || first_sync?)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ defmodule AutoSyncChannelTest do
|
|||
test_pid = self()
|
||||
|
||||
expect(Query, :auto_sync?, 2, fn -> false end)
|
||||
expect(Query, :first_sync?, 2, fn -> false end)
|
||||
|
||||
expect(API, :get_changeset, fn _module ->
|
||||
send(test_pid, :preload_all_called)
|
||||
|
@ -148,6 +149,8 @@ defmodule AutoSyncChannelTest do
|
|||
false
|
||||
end)
|
||||
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
send(pid, {:basic_deliver, payload, %{routing_key: key}})
|
||||
assert_receive :called_auto_sync?
|
||||
end
|
||||
|
@ -158,6 +161,7 @@ defmodule AutoSyncChannelTest do
|
|||
payload = '{"args":{"label":"foo"},"body":{}}'
|
||||
key = "bot.device_15.sync.Device.999"
|
||||
stub(Query, :auto_sync?, fn -> true end)
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
stub(Command, :update, fn x, y, z ->
|
||||
send(test_pid, {:update_called, x, y, z})
|
||||
|
@ -175,6 +179,7 @@ defmodule AutoSyncChannelTest do
|
|||
key = "bot.device_15.sync.#{module_name}.999"
|
||||
|
||||
stub(Query, :auto_sync?, fn -> true end)
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
stub(Command, :update, fn x, y, z ->
|
||||
send(test_pid, {:update_called, x, y, z})
|
||||
|
@ -203,6 +208,8 @@ defmodule AutoSyncChannelTest do
|
|||
false
|
||||
end)
|
||||
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
stub(Command, :update, fn kind, id, params ->
|
||||
send(test_pid, {:update_called, kind, id, params})
|
||||
:ok
|
||||
|
@ -225,6 +232,8 @@ defmodule AutoSyncChannelTest do
|
|||
false
|
||||
end)
|
||||
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
stub(Command, :new_changeset, fn kind, id, params ->
|
||||
send(test_pid, {:new_changeset_called, kind, id, params})
|
||||
:ok
|
||||
|
@ -246,6 +255,7 @@ defmodule AutoSyncChannelTest do
|
|||
key = "bot.device_15.sync.#{module_name}.999"
|
||||
|
||||
stub(Query, :auto_sync?, fn -> true end)
|
||||
stub(Query, :first_sync?, fn -> false end)
|
||||
|
||||
stub(Command, :update, fn x, y, z ->
|
||||
send(test_pid, {:update_called, x, y, z})
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule FarmbotExt.API.PreloaderTest do
|
|||
|
||||
alias FarmbotCore.{
|
||||
# Asset,
|
||||
# Asset.Query,
|
||||
Asset.Query,
|
||||
Asset.Sync
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ defmodule FarmbotExt.API.PreloaderTest do
|
|||
{:error, "some descriptive API error"}
|
||||
end)
|
||||
|
||||
expect(Query, :first_sync?, 1, fn -> false end)
|
||||
|
||||
assert {:error, "some descriptive API error"} = Preloader.preload_all()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,6 +118,7 @@ defmodule FarmbotFirmware do
|
|||
| :emergency_lock
|
||||
|
||||
defstruct [
|
||||
:configured,
|
||||
:transport,
|
||||
:transport_pid,
|
||||
:transport_ref,
|
||||
|
@ -148,7 +149,8 @@ defmodule FarmbotFirmware do
|
|||
current: nil | GCODE.t(),
|
||||
vcr_fd: nil | File.io_device(),
|
||||
reset: module(),
|
||||
reset_pid: nil | pid()
|
||||
reset_pid: nil | pid(),
|
||||
configured: boolean()
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
@ -259,6 +261,7 @@ defmodule FarmbotFirmware do
|
|||
transport_args = Keyword.put(args, :handle_gcode, fun)
|
||||
|
||||
state = %State{
|
||||
configured: false,
|
||||
transport_pid: nil,
|
||||
transport_ref: nil,
|
||||
transport: transport,
|
||||
|
@ -310,6 +313,7 @@ defmodule FarmbotFirmware do
|
|||
"Firmware Transport #{state.transport} started. #{inspect(state.transport_args)}"
|
||||
)
|
||||
|
||||
_ = side_effects(state, :handle_configuration_status, [false])
|
||||
state = goto(%{state | transport_pid: pid, transport_ref: ref}, :boot)
|
||||
{:noreply, state}
|
||||
|
||||
|
@ -340,6 +344,7 @@ defmodule FarmbotFirmware do
|
|||
|
||||
def handle_info(:timeout, %{configuration_queue: [code | rest]} = state) do
|
||||
# Logger.debug("Starting next configuration code: #{inspect(code)}")
|
||||
_ = side_effects(state, :handle_configuration_status, [false])
|
||||
|
||||
case GenServer.call(state.transport_pid, {state.tag, code}) do
|
||||
:ok ->
|
||||
|
@ -391,6 +396,7 @@ defmodule FarmbotFirmware do
|
|||
def handle_call(:close_transport, _from, %{status: s} = state) when s != :transport_boot do
|
||||
true = Process.demonitor(state.transport_ref)
|
||||
:ok = GenServer.stop(state.transport_pid, :normal)
|
||||
_ = side_effects(state, :handle_configuration_status, [false])
|
||||
|
||||
state =
|
||||
goto(
|
||||
|
@ -559,21 +565,6 @@ defmodule FarmbotFirmware do
|
|||
{:parameter_read_all, []}
|
||||
]
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_z] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:z]}],
|
||||
else: to_process
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_y] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:y]}],
|
||||
else: to_process
|
||||
|
||||
to_process =
|
||||
if loaded_params[:movement_home_at_boot_x] == 1,
|
||||
do: to_process ++ [{:command_movement_find_home, [:x]}],
|
||||
else: to_process
|
||||
|
||||
send(self(), :timeout)
|
||||
{:noreply, goto(%{state | tag: tag, configuration_queue: to_process}, :configuration)}
|
||||
end
|
||||
|
@ -694,10 +685,14 @@ defmodule FarmbotFirmware do
|
|||
# report_parameters_complete => goto(:configuration, :idle)
|
||||
def handle_report({:report_parameters_complete, []}, %{status: status} = state)
|
||||
when status in [:begin, :configuration] do
|
||||
{:noreply, goto(state, :idle)}
|
||||
Logger.debug("Firmware configuration complete")
|
||||
new_state = %{state | configured: true}
|
||||
_ = side_effects(new_state, :handle_configuration_status, [true])
|
||||
{:noreply, goto(new_state, :idle)}
|
||||
end
|
||||
|
||||
def handle_report(_, %{status: :no_config} = state) do
|
||||
_ = side_effects(state, :handle_configuration_status, [false])
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -39,4 +39,6 @@ defmodule FarmbotFirmware.SideEffects do
|
|||
@callback handle_input_gcode(GCODE.t()) :: any()
|
||||
@callback handle_output_gcode(GCODE.t()) :: any()
|
||||
@callback handle_debug_message([String.t()]) :: any()
|
||||
|
||||
@callback handle_configuration_status(boolean()) :: any()
|
||||
end
|
||||
|
|
|
@ -164,4 +164,7 @@ defmodule FarmbotFirmware.StubSideEffects do
|
|||
|
||||
@impl SideEffects
|
||||
def handle_debug_message(_), do: :noop
|
||||
|
||||
@impl SideEffects
|
||||
def handle_configuration_status(_), do: :noop
|
||||
end
|
||||
|
|
|
@ -92,6 +92,10 @@ config :farmbot_core, FarmbotCore.Asset.Repo,
|
|||
|
||||
config :farmbot_telemetry, file: to_charlist(Path.join(data_path, 'farmbot-telemetry.dets'))
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: Path.join(data_path, "farmware")
|
||||
|
||||
config :farmbot, FarmbotOS.Platform.Supervisor,
|
||||
platform_children: [
|
||||
FarmbotOS.Platform.Target.NervesHubClient,
|
||||
|
|
|
@ -92,6 +92,10 @@ config :farmbot_core, FarmbotCore.Asset.Repo,
|
|||
|
||||
config :farmbot_telemetry, file: to_charlist(Path.join(data_path, 'farmbot-telemetry.dets'))
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: Path.join(data_path, "farmware")
|
||||
|
||||
config :farmbot, FarmbotOS.Platform.Supervisor,
|
||||
platform_children: [
|
||||
FarmbotOS.Platform.Target.NervesHubClient,
|
||||
|
|
|
@ -10,7 +10,8 @@ defmodule FarmbotOS do
|
|||
{FarmbotOS.Configurator.Supervisor, []},
|
||||
{FarmbotOS.Init.Supervisor, []},
|
||||
{FarmbotOS.Platform.Supervisor, []},
|
||||
{FarmbotOS.EasterEggs, []}
|
||||
{FarmbotOS.EasterEggs, []},
|
||||
{FarmbotOS.BootupSequenceWorker, []}
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: __MODULE__]
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
defmodule FarmbotOS.BootupSequenceWorker do
|
||||
use GenServer
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
alias FarmbotCore.{Asset, BotState, DepTracker}
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
FarmwareInstalation,
|
||||
Peripheral
|
||||
}
|
||||
|
||||
alias FarmbotCeleryScript.AST
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(_args) do
|
||||
# all_required_farmwares =
|
||||
# FarmbotCore.Asset.Repo.all(FarmbotCore.Asset.FirstPartyFarmware) ++
|
||||
# FarmbotCore.Asset.Repo.all(FarmbotCore.Asset.FarmwareInstallation)
|
||||
%{
|
||||
informational_settings: %{
|
||||
sync_status: sync_status,
|
||||
idle: fw_idle,
|
||||
firmware_version: fw_version,
|
||||
firmware_configured: fw_configured
|
||||
}
|
||||
} = BotState.subscribe()
|
||||
|
||||
state = %{
|
||||
synced: sync_status == "synced",
|
||||
firmware_idle: fw_idle,
|
||||
firmware_version: fw_version,
|
||||
firmware_configured: fw_configured,
|
||||
farmwares_loaded: false,
|
||||
sequence_id: nil,
|
||||
sequence_started_at: nil,
|
||||
sequence_completed_at: nil,
|
||||
sequence_ref: nil
|
||||
}
|
||||
|
||||
# send self(), :checkup
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_info(:checkup, state) do
|
||||
Logger.debug("| bootup sequence | checkup ok")
|
||||
{:noreply, maybe_start_sequence(state)}
|
||||
end
|
||||
|
||||
def handle_info(:start_sequence, %{sequence_id: id} = state) do
|
||||
Logger.debug("| bootup sequence | start_sequence begin")
|
||||
|
||||
case Asset.get_sequence(id) do
|
||||
nil ->
|
||||
FarmbotCore.Logger.error(1, """
|
||||
Farmbot could not execute it's configured bootup sequence. Maybe
|
||||
a sync is required?
|
||||
""")
|
||||
|
||||
{:noreply, state}
|
||||
|
||||
%{name: name} ->
|
||||
Logger.debug("| bootup sequence | start_sequence ok #{inspect(state)}")
|
||||
FarmbotCore.Logger.busy(2, "Starting bootup sequence: #{name}")
|
||||
ref = make_ref()
|
||||
ast = execute_ast(id)
|
||||
worker_pid = self()
|
||||
_pid = spawn(FarmbotCeleryScript.StepRunner, :step, [worker_pid, ref, ast])
|
||||
{:noreply, %{state | sequence_started_at: DateTime.utc_now(), sequence_ref: ref}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, ref, :ok}, %{sequence_ref: ref} = state) do
|
||||
FarmbotCore.Logger.success(2, "Bootup sequence complete")
|
||||
{:noreply, %{state | sequence_completed_at: DateTime.utc_now()}}
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, ref, {:error, reason}}, %{sequence_ref: ref} = state) do
|
||||
FarmbotCore.Logger.error(2, "Bootup sequence failed: #{reason}")
|
||||
{:noreply, %{state | sequence_completed_at: DateTime.utc_now()}}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{BotState, %{changes: %{informational_settings: %{changes: %{idle: idle}}}}},
|
||||
state
|
||||
) do
|
||||
Logger.debug("| bootup sequence | idle ok")
|
||||
state = maybe_start_sequence(%{state | firmware_idle: idle})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{BotState,
|
||||
%{changes: %{informational_settings: %{changes: %{firmware_version: fw_version}}}}},
|
||||
state
|
||||
) do
|
||||
state = maybe_start_sequence(%{state | firmware_version: fw_version})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{BotState,
|
||||
%{changes: %{informational_settings: %{changes: %{firmware_configured: fw_configured}}}}},
|
||||
state
|
||||
) do
|
||||
# this should really be fixed upstream not to dispatch if version is none.
|
||||
if state.firmware_version == "none" do
|
||||
Logger.debug("| bootup sequence | firmware_configured err")
|
||||
{:noreply, state}
|
||||
else
|
||||
Logger.debug("| bootup sequence | firmware_configured ok")
|
||||
state = maybe_start_sequence(%{state | firmware_configured: fw_configured})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(
|
||||
{BotState, %{changes: %{informational_settings: %{changes: %{sync_status: "synced"}}}}},
|
||||
state
|
||||
) do
|
||||
Logger.debug("| bootup sequence | synced ok")
|
||||
state = maybe_start_sequence(%{state | synced: true})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# def handle_info({BotState, _}, state) do
|
||||
def handle_info(_, state) do
|
||||
state = maybe_start_sequence(state)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp maybe_start_sequence(%{synced: false} = state), do: state
|
||||
defp maybe_start_sequence(%{firmware_version: "none"} = state), do: state
|
||||
defp maybe_start_sequence(%{firmware_idle: false} = state), do: state
|
||||
defp maybe_start_sequence(%{firmware_configured: false} = state), do: state
|
||||
defp maybe_start_sequence(%{sequence_started_at: %DateTime{}} = state), do: state
|
||||
defp maybe_start_sequence(%{sequence_completed_at: %DateTime{}} = state), do: state
|
||||
|
||||
defp maybe_start_sequence(%{farmwares_loaded: false} = state) do
|
||||
farmwares = FarmbotCore.DepTracker.get_asset(FarmbotCore.Asset.FarmwareInstallation)
|
||||
|
||||
loaded =
|
||||
Enum.all?(farmwares, fn
|
||||
{{FarmbotCore.Asset.FarmwareInstallation, _id}, :complete} -> true
|
||||
_ -> false
|
||||
end)
|
||||
|
||||
loaded && Logger.debug("| bootup sequence | farmware_loaded ok")
|
||||
|
||||
if loaded,
|
||||
do: maybe_start_sequence(%{state | farmwares_loaded: true}),
|
||||
else: state
|
||||
end
|
||||
|
||||
defp maybe_start_sequence(state) do
|
||||
case Asset.fbos_config() do
|
||||
%{boot_sequence_id: nil} ->
|
||||
Logger.debug("| bootup sequnce | assets_loaded noop")
|
||||
state
|
||||
|
||||
%{boot_sequence_id: id} ->
|
||||
loaded? = dependency_assets_loaded?()
|
||||
Logger.debug("| bootup sequnce | assets_loaded ok")
|
||||
loaded? && send(self(), :start_sequence)
|
||||
%{state | sequence_id: id}
|
||||
end
|
||||
end
|
||||
|
||||
defp execute_ast(sequence_id) do
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("fbos_config.bootup_sequence")
|
||||
|> AST.Factory.execute(sequence_id)
|
||||
end
|
||||
|
||||
defp dependency_assets_loaded?() do
|
||||
peripherals =
|
||||
Enum.all?(DepTracker.get_asset(Peripheral), fn
|
||||
{{Peripheral, _}, :complete} ->
|
||||
true
|
||||
|
||||
{{kind, id}, status} ->
|
||||
Logger.debug("bootup sequence still waiting on: #{kind}.#{id} status=#{status}")
|
||||
false
|
||||
end)
|
||||
|
||||
farmware =
|
||||
Enum.all?(DepTracker.get_asset(FarmwareInstalation), fn
|
||||
{{FarmwareInstalation, _}, :complete} ->
|
||||
true
|
||||
|
||||
{{kind, id}, status} ->
|
||||
Logger.debug("bootup sequence still waiting on: #{kind}.#{id} status=#{status}")
|
||||
false
|
||||
end)
|
||||
|
||||
peripherals && farmware
|
||||
end
|
||||
end
|
|
@ -67,7 +67,7 @@ defmodule FarmbotOS.MixProject do
|
|||
def application do
|
||||
[
|
||||
mod: {FarmbotOS, []},
|
||||
extra_applications: [:logger, :runtime_tools, :eex]
|
||||
extra_applications: [:logger, :runtime_tools, :eex, :inets]
|
||||
]
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue