diff --git a/farmbot_core/lib/farmbot_core/asset_workers/fbos_config_worker.ex b/farmbot_core/lib/farmbot_core/asset_workers/fbos_config_worker.ex index 77f91b17..076edf71 100644 --- a/farmbot_core/lib/farmbot_core/asset_workers/fbos_config_worker.ex +++ b/farmbot_core/lib/farmbot_core/asset_workers/fbos_config_worker.ex @@ -14,7 +14,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do @firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold] @firmware_flash_timeout Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_timeout] || 5000 @disable_firmware_io_logs_timeout Application.get_env(:farmbot_core, __MODULE__)[:disable_firmware_io_logs_timeout] || 300000 - @retry_bootup_sequence_ms 2000 @firmware_flash_attempt_threshold || Mix.raise """ Firmware open attempt threshold not configured: @@ -58,19 +57,15 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do firmware_idle: fw_idle, firmware_version: fw_version, firmware_configured: fw_configured, - boot_sequence_started: nil, - boot_sequence_completed: nil, - boot_sequence_ref: nil, } {:ok, state, 0} end @impl GenServer def handle_info({:step_complete, ref, :ok}, %{firmware_flash_ref: ref} = state) do - DepTracker.register_asset(state.fbos_config, :bootup_sequence) + 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) - send self(), :bootup_sequence {:noreply, %{state | firmware_flash_in_progress: false}} end @@ -108,18 +103,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do {:noreply, new_state} end - def handle_info({:step_complete, ref, :ok}, %{boot_sequence_ref: ref} = state) do - :ok = DepTracker.register_asset(state.fbos_config, :idle) - FarmbotCore.Logger.success 2, "Bootup sequence complete" - {:noreply, %{state | boot_sequence_completed: DateTime.utc_now()}} - end - - def handle_info({:step_complete, ref, {:error, reason}}, %{boot_sequence_ref: ref} = state) do - :ok = DepTracker.register_asset(state.fbos_config, :idle) - FarmbotCore.Logger.error 2, "Bootup sequence failed: #{reason}" - {:noreply, %{state | boot_sequence_completed: DateTime.utc_now()}} - end - def handle_info(:timeout, %{fbos_config: %FbosConfig{} = fbos_config} = state) do FarmbotCore.Logger.debug 3, "Got initial fbos config" set_config_to_state(fbos_config) @@ -172,52 +155,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do {:noreply, %{state | fbos_config: new_fbos_config, firmware_io_timer: nil}} end - def handle_info(:bootup_sequence, %{fbos_config: %{boot_sequence_id: nil}} = state) do - DepTracker.register_asset(state.fbos_config, :idle) - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{firmware_version: nil} = state) do - # Logger.debug("Not executing bootup sequence. Firmware not booted.") - Process.send_after(self(), :bootup_sequence, @retry_bootup_sequence_ms) - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{firmware_version: "none"} = state) do - # Logger.debug("Not executing bootup sequence. Firmware not booted.") - Process.send_after(self(), :bootup_sequence, @retry_bootup_sequence_ms) - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{firmware_configured: false} = state) do - # Logger.debug("Not executing bootup sequence. Firmware not configured.") - Process.send_after(self(), :bootup_sequence, @retry_bootup_sequence_ms) - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{firmware_idle: false} = state) do - # Logger.debug("Not executing bootup sequence. Firmware not idle.") - Process.send_after(self(), :bootup_sequence, @retry_bootup_sequence_ms) - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{boot_sequence_started: %DateTime{}} = state) do - {:noreply, state} - end - - def handle_info(:bootup_sequence, %{fbos_config: %{boot_sequence_id: id}} = state) do - case Asset.get_sequence(id) do - nil -> - Process.send_after(self(), :bootup_sequence, 5000) - {:noreply, state} - _ -> - FarmbotCore.Logger.busy 3, "Executing bootup sequence" - ref = make_ref() - FarmbotCeleryScript.execute(execute_ast(id), ref) - {:noreply, %{state | boot_sequence_started: DateTime.utc_now(), boot_sequence_ref: ref}} - end - end - def handle_info({BotState, %{changes: %{informational_settings: %{changes: %{idle: idle}}}}}, state) do {:noreply, %{state | firmware_idle: idle}} end @@ -280,8 +217,8 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do ref true -> + DepTracker.register_asset(state.fbos_config, :idle) # Config.update_config_value(:bool, "settings", "firmware_needs_open", true) - send self(), :bootup_sequence nil end end @@ -389,10 +326,4 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do |> AST.Factory.rpc_request("fbos_config.flash_firmware") |> AST.Factory.flash_firmware(firmware_hardware) end - - def execute_ast(sequence_id) do - AST.Factory.new() - |> AST.Factory.rpc_request("fbos_config.bootup_sequence") - |> AST.Factory.execute(sequence_id) - end end diff --git a/farmbot_core/lib/farmbot_core/asset_workers/peripheral_worker.ex b/farmbot_core/lib/farmbot_core/asset_workers/peripheral_worker.ex index d5b03c15..af5e2fe6 100644 --- a/farmbot_core/lib/farmbot_core/asset_workers/peripheral_worker.ex +++ b/farmbot_core/lib/farmbot_core/asset_workers/peripheral_worker.ex @@ -28,6 +28,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do @impl true def init(peripheral) do + :ok = DepTracker.register_asset(peripheral, :init) %{ informational_settings: %{ idle: idle, @@ -95,6 +96,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do 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} diff --git a/farmbot_core/lib/farmbot_core/bot_state.ex b/farmbot_core/lib/farmbot_core/bot_state.ex index 73cdc29a..cee8964e 100644 --- a/farmbot_core/lib/farmbot_core/bot_state.ex +++ b/farmbot_core/lib/farmbot_core/bot_state.ex @@ -73,7 +73,7 @@ defmodule FarmbotCore.BotState do GenServer.call(bot_state_server, {:set_firmware_version, version}) end - def set_firmware_configured(bot_state_server \\ __MODULE__, configured \\ true) do + def set_firmware_configured(bot_state_server \\ __MODULE__, configured) do GenServer.call(bot_state_server, {:set_firmware_configured, configured}) end diff --git a/farmbot_core/lib/farmbot_core/firmware_side_effects.ex b/farmbot_core/lib/farmbot_core/firmware_side_effects.ex index 05ccdb02..5c658a1a 100644 --- a/farmbot_core/lib/farmbot_core/firmware_side_effects.ex +++ b/farmbot_core/lib/farmbot_core/firmware_side_effects.ex @@ -167,8 +167,8 @@ defmodule FarmbotCore.FirmwareSideEffects do end @impl FarmbotFirmware.SideEffects - def handle_configuration_complete() do - :ok = BotState.set_firmware_configured() + def handle_configuration_status(status) do + :ok = BotState.set_firmware_configured(status) DepTracker.register_service(:firmware, :configured) end diff --git a/farmbot_firmware/lib/farmbot_firmware.ex b/farmbot_firmware/lib/farmbot_firmware.ex index d9e4b47d..82998b55 100644 --- a/farmbot_firmware/lib/farmbot_firmware.ex +++ b/farmbot_firmware/lib/farmbot_firmware.ex @@ -313,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} @@ -343,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 -> @@ -394,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( @@ -684,11 +687,12 @@ defmodule FarmbotFirmware do when status in [:begin, :configuration] do Logger.debug("Firmware configuration complete") new_state = %{state | configured: true} - _ = side_effects(new_state, :handle_configuration_complete, []) + _ = 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 diff --git a/farmbot_firmware/lib/farmbot_firmware/side_effects.ex b/farmbot_firmware/lib/farmbot_firmware/side_effects.ex index 8c9ed12e..0eda9ec9 100644 --- a/farmbot_firmware/lib/farmbot_firmware/side_effects.ex +++ b/farmbot_firmware/lib/farmbot_firmware/side_effects.ex @@ -40,5 +40,5 @@ defmodule FarmbotFirmware.SideEffects do @callback handle_output_gcode(GCODE.t()) :: any() @callback handle_debug_message([String.t()]) :: any() - @callback handle_configuration_complete() :: any() + @callback handle_configuration_status(boolean()) :: any() end diff --git a/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex b/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex index 5ed663af..2106960d 100644 --- a/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex +++ b/farmbot_firmware/lib/farmbot_firmware/stub_side_effects.ex @@ -166,5 +166,5 @@ defmodule FarmbotFirmware.StubSideEffects do def handle_debug_message(_), do: :noop @impl SideEffects - def handle_configuration_complete(), do: :noop + def handle_configuration_status(_), do: :noop end diff --git a/farmbot_os/lib/farmbot_os.ex b/farmbot_os/lib/farmbot_os.ex index 02bdecd6..ddd69acf 100644 --- a/farmbot_os/lib/farmbot_os.ex +++ b/farmbot_os/lib/farmbot_os.ex @@ -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__] diff --git a/farmbot_os/lib/farmbot_os/bootup_sequence_worker.ex b/farmbot_os/lib/farmbot_os/bootup_sequence_worker.ex new file mode 100644 index 00000000..46d72cef --- /dev/null +++ b/farmbot_os/lib/farmbot_os/bootup_sequence_worker.ex @@ -0,0 +1,139 @@ +defmodule FarmbotOS.BootupSequenceWorker do + use GenServer + require Logger + require FarmbotCore.Logger + alias FarmbotCore.{Asset, BotState, DepTracker} + alias FarmbotCore.Asset.Peripheral + alias FarmbotCeleryScript.AST + + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + def init(_args) do + %{ + 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, + 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 + {:noreply, maybe_start_sequence(state)} + end + + def handle_info(:start_sequence, %{sequence_id: id} = state) do + case Asset.get_sequence(id) do + nil -> + {:stop, "could not find bootup sequence by id: #{inspect(id)}"} + + %{name: name} -> + Logger.debug("bootup sequence start: #{inspect(state)}") + FarmbotCore.Logger.busy(2, "Starting bootup sequence: #{name}") + ref = make_ref() + FarmbotCeleryScript.execute(execute_ast(id), ref) + {: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 + 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 + {:noreply, state} + else + 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 + state = maybe_start_sequence(%{state | synced: true}) + {:noreply, state} + end + + def handle_info({BotState, _}, state) do + state = maybe_start_sequence(%{state | synced: true}) + {: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(state) do + case Asset.fbos_config() do + %{boot_sequence_id: nil} -> + state + + %{boot_sequence_id: id} -> + peripherals_loaded? = + Enum.all?(DepTracker.get_asset(Peripheral), fn + {{Peripheral, _}, :complete} -> true + _ -> false + end) + + peripherals_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 +end