farmbot_os/farmbot_core/lib/farmbot_core/bot_state.ex

527 lines
15 KiB
Elixir

defmodule FarmbotCore.BotState do
@moduledoc "Central State accumulator."
alias FarmbotCore.BotStateNG
require FarmbotCore.Logger
use GenServer
@doc "Subscribe to BotState changes"
def subscribe(bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, :subscribe)
end
@doc "Set job progress."
def set_job_progress(bot_state_server \\ __MODULE__, name, progress) do
GenServer.call(bot_state_server, {:set_job_progress, name, progress})
end
@doc "Set a configuration value"
def set_config_value(bot_state_server \\ __MODULE__, key, value) do
GenServer.call(bot_state_server, {:set_config_value, key, value})
end
@doc "Sets user_env value"
def set_user_env(bot_state_server \\ __MODULE__, key, value) do
GenServer.call(bot_state_server, {:set_user_env, key, value})
end
@doc "Sets the location_data.position"
def set_position(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_position, x, y, z})
end
@doc "Sets the location_data.load"
def set_load(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_load, x, y, z})
end
@doc "Sets the location_data.encoders_scaled"
def set_encoders_scaled(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_encoders_scaled, x, y, z})
end
@doc "Sets the location_data.encoders_raw"
def set_encoders_raw(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_encoders_raw, x, y, z})
end
def set_axis_state(bot_state_server \\ __MODULE__, axis, state) do
GenServer.call(bot_state_server, {:set_axis_state, axis, state})
end
@doc "Sets pins.pin.value"
def set_pin_value(bot_state_server \\ __MODULE__, pin, value) do
GenServer.call(bot_state_server, {:set_pin_value, pin, value})
end
@doc "Sets mcu_params[param] = value"
def set_firmware_config(bot_state_server \\ __MODULE__, param, value) do
GenServer.call(bot_state_server, {:set_firmware_config, param, value})
end
@doc "Sets informational_settings.locked = true"
def set_firmware_locked(bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, {:set_firmware_locked, true})
end
@doc "Sets informational_settings.locked = false"
def set_firmware_unlocked(bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, {:set_firmware_locked, false})
end
@doc "Sets informational_settings.firmware_version"
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
GenServer.call(bot_state_server, {:set_firmware_hardware, hardware})
end
@doc "Sets informational_settings.busy"
def set_firmware_busy(bot_state_server \\ __MODULE__, busy) do
GenServer.call(bot_state_server, {:set_firmware_busy, busy})
end
@doc "Sets informational_settings.idle"
def set_firmware_idle(bot_state_server \\ __MODULE__, idle) do
GenServer.call(bot_state_server, {:set_firmware_idle, idle})
end
@doc "Sets informational_settings.status"
def set_sync_status(bot_state_server \\ __MODULE__, s)
when s in ["sync_now", "syncing", "synced", "sync_error", "maintenance"] do
GenServer.call(bot_state_server, {:set_sync_status, s})
end
@doc "sets informational_settings.update_available"
def set_update_available(bot_state_server \\ __MODULE__, bool)
when is_boolean(bool) do
GenServer.call(bot_state_server, {:set_update_available, bool})
end
@doc "sets informational_settings.node_name"
def set_node_name(bot_state_server \\ __MODULE__, node_name)
when is_binary(node_name) do
GenServer.call(bot_state_server, {:set_node_name, node_name})
end
@doc "sets informational_settings.private_ip"
def set_private_ip(bot_state_server \\ __MODULE__, private_ip) do
GenServer.call(bot_state_server, {:set_private_ip, private_ip})
end
def set_controller_uuid(bot_state_server \\ __MODULE__, uuid) do
GenServer.call(bot_state_server, {:set_controller_uuid, uuid})
end
@doc "Fetch the current state."
def fetch(bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, :fetch)
end
def report_disk_usage(bot_state_server \\ __MODULE__, percent) do
GenServer.call(bot_state_server, {:report_disk_usage, percent})
end
def report_memory_usage(bot_state_server \\ __MODULE__, megabytes) do
GenServer.call(bot_state_server, {:report_memory_usage, megabytes})
end
def report_scheduler_usage(bot_state_server \\ __MODULE__, percent) do
GenServer.call(bot_state_server, {:report_scheduler_usage, percent})
end
def report_soc_temp(bot_state_server \\ __MODULE__, temp_celcius) do
GenServer.call(bot_state_server, {:report_soc_temp, temp_celcius})
end
def report_throttled(bot_state_server \\ __MODULE__, throttled_str) do
GenServer.call(bot_state_server, {:report_throttled, throttled_str})
end
def report_uptime(bot_state_server \\ __MODULE__, seconds) do
GenServer.call(bot_state_server, {:report_uptime, seconds})
end
def report_wifi_level(bot_state_server \\ __MODULE__, level) do
GenServer.call(bot_state_server, {:report_wifi_level, level})
end
def report_wifi_level_percent(bot_state_server \\ __MODULE__, percent) do
GenServer.call(bot_state_server, {:report_wifi_level_percent, percent})
end
def report_farmware_installed(bot_state_server \\ __MODULE__, name, %{} = manifest) do
GenServer.call(bot_state_server, {:report_farmware_installed, name, manifest})
end
@doc "Put FBOS into maintenance mode."
def enter_maintenance_mode(bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, :enter_maintenance_mode)
end
@doc false
def start_link(args, opts \\ [name: __MODULE__]) do
GenServer.start_link(__MODULE__, args, opts)
end
@doc false
def init([]) do
{:ok, %{tree: BotStateNG.new(), subscribers: []}}
end
def terminate(reason, _state) do
FarmbotCore.Logger.error 1, "BotState crashed! #{inspect(reason)}"
end
@doc false
def handle_call(:subscribe, {pid, _} = _from, state) do
# TODO Just replace this with Elixir.Registry?
# Process.link(pid)
{:reply, state.tree, %{state | subscribers: Enum.uniq([pid | state.subscribers])}}
end
def handle_call(:fetch, _from, state) do
{:reply, state.tree, state}
end
def handle_call({:set_job_progress, name, progress}, _from, state) do
{reply, state} =
BotStateNG.set_job_progress(state.tree, name, Map.from_struct(progress))
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_config_value, key, value}, _from, state) do
change = %{configuration: %{key => value}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_user_env, key, value}, _from, state) do
{reply, state} =
BotStateNG.set_user_env(state.tree, key, value)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_position, x, y, z}, _from, state) do
change = %{location_data: %{position: %{x: x, y: y, z: z}}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_load, x, y, z}, _from, state) do
change = %{location_data: %{load: %{x: x, y: y, z: z}}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_encoders_scaled, x, y, z}, _from, state) do
change = %{location_data: %{scaled_encoders: %{x: x, y: y, z: z}}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_encoders_raw, x, y, z}, _from, state) do
change = %{location_data: %{raw_encoders: %{x: x, y: y, z: z}}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_axis_state, axis, axis_state}, _from, state) do
change = %{
location_data: %{
axis_states: %{
axis => to_string(axis_state)
}
}
}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_pin_value, pin, value}, _from, state) do
{reply, state} =
BotStateNG.add_or_update_pin(state.tree, pin, -1, value)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_firmware_config, param, value}, _from, state) do
change = %{mcu_params: %{param => value}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_firmware_locked, bool}, _from, state) do
change = %{informational_settings: %{locked: bool}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_firmware_version, version}, _from, state) do
change = %{informational_settings: %{firmware_version: version}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{: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}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_firmware_busy, busy}, _from, state) do
change = %{informational_settings: %{busy: busy}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_firmware_idle, idle}, _from, state) do
change = %{informational_settings: %{idle: idle}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_sync_status, status}, _from, state) do
change = %{informational_settings: %{sync_status: status}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_update_available, bool}, _from, state) do
change = %{informational_settings: %{update_available: bool}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_node_name, node_name}, _from, state) do
change = %{informational_settings: %{node_name: node_name}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_private_ip, private_ip}, _from, state) do
change = %{informational_settings: %{private_ip: private_ip}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:set_controller_uuid, controller_uuid}, _from, state) do
change = %{informational_settings: %{controller_uuid: controller_uuid}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_disk_usage, percent}, _form, state) do
change = %{informational_settings: %{disk_usage: percent}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_memory_usage, megabytes}, _form, state) do
change = %{informational_settings: %{memory_usage: megabytes}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_scheduler_usage, average_percent}, _form, state) do
change = %{informational_settings: %{scheduler_usage: average_percent}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_soc_temp, temp}, _form, state) do
change = %{informational_settings: %{soc_temp: temp}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_throttled, throttled_str}, _form, state) do
change = %{informational_settings: %{throttled: throttled_str}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_uptime, seconds}, _form, state) do
change = %{informational_settings: %{uptime: seconds}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_wifi_level, level}, _form, state) do
change = %{informational_settings: %{wifi_level: level}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_wifi_level_percent, percent}, _form, state) do
change = %{informational_settings: %{wifi_level_percent: percent}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call({:report_farmware_installed, name, manifest}, _from, state) do
{reply, state} =
BotStateNG.add_or_update_farmware(state.tree, name, manifest)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
def handle_call(:enter_maintenance_mode, _form, state) do
change = %{informational_settings: %{sync_status: "maintenance"}}
{reply, state} =
BotStateNG.changeset(state.tree, change)
|> dispatch_and_apply(state)
{:reply, reply, state}
end
defp dispatch_and_apply(%Ecto.Changeset{changes: changes}, state) when map_size(changes) == 0 do
{:ok, state}
end
defp dispatch_and_apply(%Ecto.Changeset{valid?: true} = change, state) do
state = %{state | tree: Ecto.Changeset.apply_changes(change)}
state =
Enum.reduce(state.subscribers, state, fn pid, state ->
if Process.alive?(pid) do
send(pid, {__MODULE__, change})
state
else
Process.unlink(pid)
%{state | subscribers: List.delete(state.subscribers, pid)}
end
end)
{:ok, state}
end
defp dispatch_and_apply(%Ecto.Changeset{valid?: false} = change, state) do
{{:error, change}, state}
end
end