farmbot_os/farmbot_core/lib/bot_state/bot_state.ex

260 lines
8.4 KiB
Elixir

defmodule Farmbot.BotState do
@moduledoc "Central State accumulator."
alias Farmbot.BotState
alias BotState.{
McuParams,
LocationData,
Configuration,
InformationalSettings,
Pin
}
defstruct [
mcu_params: struct(McuParams),
location_data: struct(LocationData),
configuration: struct(Configuration),
informational_settings: struct(InformationalSettings),
pins: %{},
process_info: %{farmwares: %{}},
gpio_registry: %{},
user_env: %{},
jobs: %{}
]
use GenStage
def download_progress_fun(name) do
alias Farmbot.BotState.JobProgress
require Farmbot.Logger
fn(bytes, total) ->
{do_send, prog} = cond do
# if the total is complete spit out the bytes,
# and put a status of complete.
total == :complete ->
Farmbot.Logger.success 3, "#{name} complete."
{true, %JobProgress.Bytes{bytes: bytes, status: :complete}}
# if we don't know the total just spit out the bytes.
total == nil ->
# debug_log "#{name} - #{bytes} bytes."
{rem(bytes, 10) == 0, %JobProgress.Bytes{bytes: bytes}}
# if the number of bytes == the total bytes,
# percentage side is complete.
(div(bytes, total)) == 1 ->
Farmbot.Logger.success 3, "#{name} complete."
{true, %JobProgress.Percent{percent: 100, status: :complete}}
# anything else is a percent.
true ->
percent = ((bytes / total) * 100) |> round()
# Logger.busy 3, "#{name} - #{bytes}/#{total} = #{percent}%"
{rem(percent, 10) == 0, %JobProgress.Percent{percent: percent}}
end
if do_send do
Farmbot.BotState.set_job_progress(name, prog)
else
:ok
end
end
end
@doc "Set job progress."
def set_job_progress(name, progress) do
GenServer.call(__MODULE__, {:set_job_progress, name, progress})
end
def clear_progress_fun(name) do
GenServer.call(__MODULE__, {:clear_progress_fun, name})
end
@doc "Fetch the current state."
def fetch do
GenStage.call(__MODULE__, :fetch)
end
def report_disk_usage(percent) when is_number(percent) do
GenStage.call(__MODULE__, {:report_disk_usage, percent})
end
def report_memory_usage(megabytes) when is_number(megabytes) do
GenStage.call(__MODULE__, {:report_memory_usage, megabytes})
end
def report_soc_temp(temp_celcius) when is_number(temp_celcius) do
GenStage.call(__MODULE__, {:report_soc_temp, temp_celcius})
end
def report_uptime(seconds) when is_number(seconds) do
GenStage.call(__MODULE__, {:report_uptime, seconds})
end
def report_wifi_level(level) when is_number(level) do
GenStage.call(__MODULE__, {:report_wifi_level, level})
end
@doc "Put FBOS into maintenance mode."
def enter_maintenance_mode do
GenStage.call(__MODULE__, :enter_maintenance_mode)
end
@doc false
def start_link(args) do
GenStage.start_link(__MODULE__, args, [name: __MODULE__])
end
@doc false
def init([]) do
Farmbot.Registry.subscribe()
send(self(), :get_initial_configuration)
send(self(), :get_initial_mcu_params)
{:consumer, struct(BotState), [subscribe_to: [Farmbot.Firmware]]}
end
@doc false
def handle_call(:fetch, _from, state) do
new_state = handle_event({:informational_settings, %{cache_bust: :rand.uniform(1000)}}, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, state, [], new_state}
end
# TODO(Connor) - Fix this to use event system.
def handle_call({:set_job_progress, name, progress}, _from, state) do
jobs = Map.put(state.jobs, name, progress)
new_state = %{state | jobs: jobs}
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
# TODO(Connor) - Fix this to use event system.
def handle_call({:clear_progress_fun, name}, _from, state) do
jobs = Map.delete(state.jobs, name)
new_state = %{state | jobs: jobs}
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call({:report_disk_usage, percent}, _form, state) do
event = {:informational_settings, %{disk_usage: percent}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call({:memory_usage, megabytes}, _form, state) do
event = {:informational_settings, %{memory_usage: megabytes}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call({:report_soc_temp, temp}, _form, state) do
event = {:informational_settings, %{soc_temp: temp}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call({:uptime, seconds}, _form, state) do
event = {:informational_settings, %{uptime: seconds}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call({:report_wifi_level, level}, _form, state) do
event = {:informational_settings, %{wifi_level: level}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
def handle_call(:enter_maintenance_mode, _form, state) do
event = {:informational_settings, %{sync_status: :maintenance}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:reply, :ok, [], new_state}
end
@doc false
def handle_info({Farmbot.Registry, {Farmbot.Config, {"settings", key, val}}}, state) do
event = {:settings, %{String.to_atom(key) => val}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:noreply, [], new_state}
end
def handle_info({Farmbot.Registry, {_, {:sync_status, status}}}, state) do
event = {:informational_settings, %{sync_status: status}}
new_state = handle_event(event, state)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:noreply, [], new_state}
end
def handle_info({Farmbot.Registry, _}, state), do: {:noreply, [], state}
def handle_info(:get_initial_configuration, state) do
full_config = Farmbot.Config.get_config_as_map()
settings = full_config["settings"]
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
event = {:settings, %{String.to_atom(key) => val}}
handle_event(event, state)
end)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:noreply, [], new_state}
end
def handle_info(:get_initial_mcu_params, state) do
full_config = Farmbot.Config.get_config_as_map()
settings = full_config["hardware_params"]
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
event = {:mcu_params, %{String.to_atom(key) => val}}
handle_event(event, state)
end)
Farmbot.Registry.dispatch(__MODULE__, new_state)
{:noreply, [], new_state}
end
@doc false
def handle_events(events, _from, state) do
state = Enum.reduce(events, state, &handle_event(&1, &2))
Farmbot.Registry.dispatch(__MODULE__, state)
{:noreply, [], state}
end
@doc false
def handle_event({:informational_settings, data}, state) do
new_data = Map.merge(state.informational_settings, data) |> Map.from_struct()
new_informational_settings = struct(InformationalSettings, new_data)
%{state | informational_settings: new_informational_settings}
end
def handle_event({:mcu_params, data}, state) do
new_data = Map.merge(state.mcu_params, data) |> Map.from_struct()
new_mcu_params = struct(McuParams, new_data)
%{state | mcu_params: new_mcu_params}
end
def handle_event({:location_data, data}, state) do
new_data = Map.merge(state.location_data, data) |> Map.from_struct()
new_location_data = struct(LocationData, new_data)
%{state | location_data: new_location_data}
end
def handle_event({:pins, data}, state) do
new_data = Enum.reduce(data, state.pins, fn({number, pin_state}, pins) ->
Map.put(pins, number, struct(Pin, pin_state))
end)
%{state | pins: new_data}
end
def handle_event({:settings, data}, state) do
new_data = Map.merge(state.configuration, data) |> Map.from_struct()
new_configuration = struct(Configuration, new_data)
%{state | configuration: new_configuration}
end
def handle_event(event, state) do
IO.inspect event, label: "unhandled event"
state
end
end