Refactor bootup sequence to be in `farmbot_os` application

This moves it's execution to much later in the boot process. Doing this
allows the bootup sequence to check for a number of things before
executing. Checked systems are:

* firmware configured
* bot is synced (which happens on every boot now)
* peripherals loaded
feature/sequence-on-boot
Connor Rigby 2019-11-07 14:03:39 -08:00
parent e79e1e4d05
commit 503e62507e
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
9 changed files with 155 additions and 78 deletions

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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__]

View File

@ -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