fixed some race conditions. (why are there so many?) and hopefully optomized logging a bit
parent
b8949840fd
commit
b62fe312cc
|
@ -21,6 +21,7 @@ defmodule Farmbot.BotState do
|
|||
pins: %{},
|
||||
configuration: %{},
|
||||
informational_settings: %{},
|
||||
farm_scheduler: %Farmbot.Scheduler.State.Serializer{},
|
||||
authorization: %{
|
||||
token: nil,
|
||||
email: nil,
|
||||
|
@ -36,6 +37,7 @@ defmodule Farmbot.BotState do
|
|||
pins: %{},
|
||||
configuration: %{},
|
||||
informational_settings: %{},
|
||||
farm_scheduler: Farmbot.Scheduler.State.Serializer.t,
|
||||
authorization: %{
|
||||
token: map | nil,
|
||||
email: String.t | nil,
|
||||
|
@ -115,13 +117,14 @@ defmodule Farmbot.BotState do
|
|||
spawn fn -> apply_status(rcontents) end
|
||||
old_config = rcontents.configuration
|
||||
old_auth = rcontents.authorization
|
||||
old_sch = rcontents.farm_scheduler
|
||||
Map.put(default_state, :configuration, old_config)
|
||||
|> Map.put(:authorization, old_auth)
|
||||
end
|
||||
_ ->
|
||||
spawn fn -> apply_auth(default_state.authorization) end
|
||||
spawn fn -> apply_status(default_state) end
|
||||
default_state
|
||||
spawn fn -> apply_auth(default_state.authorization) end
|
||||
spawn fn -> apply_status(default_state) end
|
||||
default_state
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -202,6 +205,13 @@ defmodule Farmbot.BotState do
|
|||
state}
|
||||
end
|
||||
|
||||
# I HAVE NO CLUE WHAT IM DOING
|
||||
def handle_cast({:scheduler,
|
||||
%Farmbot.Scheduler.State.Serializer{} = sch_state}, state)
|
||||
do
|
||||
{:noreply, %State{state | farm_scheduler: sch_state}}
|
||||
end
|
||||
|
||||
# Lock the frontend from doing stuff
|
||||
def handle_cast({:add_lock, string}, state) do
|
||||
maybe_index = Enum.find_index(state.locks, fn(%{reason: str}) -> str == string end)
|
||||
|
@ -262,7 +272,6 @@ defmodule Farmbot.BotState do
|
|||
end
|
||||
|
||||
def handle_info({:connected, network, ip_addr}, state) do
|
||||
Process.sleep(2000) # UGH
|
||||
# GenServer.cast(Farmbot.BotState, {:update_info, :private_ip, address})
|
||||
new_info = Map.put(state.informational_settings, :private_ip, ip_addr)
|
||||
email = state.authorization.email
|
||||
|
@ -276,6 +285,7 @@ defmodule Farmbot.BotState do
|
|||
%{email: email, pass: pass, server: server, token: token,
|
||||
network: network})
|
||||
set_time
|
||||
Farmbot.node_reset(ip_addr)
|
||||
{:noreply,
|
||||
Map.put(state, :authorization, auth)
|
||||
|> Map.put(:informational_settings, new_info)
|
||||
|
@ -284,7 +294,7 @@ defmodule Farmbot.BotState do
|
|||
{:error, "enetunreach"} ->
|
||||
Logger.warn("Something super weird happened.. Probably a race condition.")
|
||||
# Just crash ourselves and try again.
|
||||
{:crash, state}
|
||||
handle_info({:connected, network, ip_addr}, state)
|
||||
error ->
|
||||
Logger.error("Something bad happened when logging in!: #{inspect error}")
|
||||
Farmbot.factory_reset
|
||||
|
|
|
@ -7,6 +7,13 @@ defmodule Farmbot do
|
|||
use Supervisor
|
||||
@state_path Application.get_env(:farmbot, :state_path)
|
||||
|
||||
def node_reset(address) do
|
||||
# ip = Farmbot.BotState.get_status |> Map.get(:informational_settings) |> Map.get(:private_ip)
|
||||
Node.stop
|
||||
full_node_name = "farmbot@#{address}" |> String.to_atom
|
||||
{:ok, _pid} = Node.start(full_node_name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shortcut to Nerves.Firmware.reboot
|
||||
"""
|
||||
|
|
|
@ -1,9 +1,72 @@
|
|||
defmodule Farmbot.Logger do
|
||||
use GenServer
|
||||
@moduledoc """
|
||||
Right now this doesn't do anything but eventually it will save log messages
|
||||
and push them to teh frontend
|
||||
"""
|
||||
def log(message, channels, tags) do
|
||||
RPC.MessageHandler.log(message, channels, tags)
|
||||
GenServer.cast(__MODULE__, {:log, message, tags, Timex.now})
|
||||
end
|
||||
|
||||
def get_all do
|
||||
GenServer.call(__MODULE__, :get_all)
|
||||
end
|
||||
|
||||
def init(_args) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
def handle_cast({:log, message, tags, time}, messages) do
|
||||
{:noreply, [{message, tags, time} | messages]}
|
||||
end
|
||||
|
||||
def handle_call(:get_all, _from, messages) do
|
||||
{:reply, Enum.reverse(messages), messages}
|
||||
end
|
||||
|
||||
def handle_call({:get, ammount}, _from, messages) do
|
||||
{:reply,
|
||||
Enum.reverse(messages)
|
||||
|> Enum.take(ammount),
|
||||
messages}
|
||||
end
|
||||
|
||||
def handle_call({:get_tag, list_of_tags}, _from, messages) do
|
||||
filtered = filter(messages, list_of_tags)
|
||||
|> Enum.map(fn({m, _, time}) -> {m, time} end)
|
||||
{:reply, filtered, messages}
|
||||
end
|
||||
|
||||
def filter(list_of_tags) when is_list(list_of_tags) do
|
||||
GenServer.call(__MODULE__, {:get_tag, list_of_tags})
|
||||
end
|
||||
|
||||
def filter(messages, []) do
|
||||
Enum.reverse messages
|
||||
end
|
||||
|
||||
def filter(messages, list_of_tags)
|
||||
when is_list(list_of_tags) do
|
||||
filter(messages, List.first(list_of_tags), list_of_tags)
|
||||
end
|
||||
|
||||
def filter(messages, tag, list_of_tags)
|
||||
when is_bitstring(tag) and is_list(list_of_tags) do
|
||||
{bleep, bloop} = Enum.partition(messages, fn({_m, tags, _}) ->
|
||||
contain_tag?(tags, tag)
|
||||
end)
|
||||
filter(messages -- bloop, list_of_tags -- [tag])
|
||||
end
|
||||
|
||||
def contain_tag?(tags, tag) do
|
||||
Enum.any?(tags, fn(t) ->
|
||||
tag == t
|
||||
end)
|
||||
end
|
||||
end
|
||||
# Filtr.filter logs, ["who cares about regimens"]
|
||||
|
|
|
@ -163,6 +163,7 @@ defmodule Regimen.VM do
|
|||
msg = "Regimen: #{state.regimen.name} completed without errors!"
|
||||
Logger.debug(msg)
|
||||
Farmbot.Logger.log(msg, [:ticker, :success_toast], ["RegimenManager"])
|
||||
RPC.MessageHandler.send_status
|
||||
end
|
||||
|
||||
# this gets called if the scheduler crashes.
|
||||
|
|
|
@ -9,6 +9,49 @@ defmodule Farmbot.Scheduler do
|
|||
"""
|
||||
|
||||
defmodule State do
|
||||
defmodule Serializer do
|
||||
@moduledoc """
|
||||
When all you want is relevant information :tm:
|
||||
"""
|
||||
@type regimen_info ::
|
||||
%{regimen: Regimen.t,
|
||||
info: %{ start_time: DateTime.t,
|
||||
status: State.regimen_flag }
|
||||
}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
process_info: [regimen_info],
|
||||
current_sequence: Sequence.t | nil,
|
||||
sequence_log: [Sequence.t]
|
||||
}
|
||||
|
||||
defstruct [
|
||||
process_info: [],
|
||||
current_sequence: nil,
|
||||
sequence_log: []
|
||||
]
|
||||
|
||||
@doc """
|
||||
Turns a state into something more readable (by the web app)
|
||||
"""
|
||||
@spec serialize(State.t) :: State.Serializer.t
|
||||
def serialize(state) do
|
||||
regimen_info_list = Enum.map(state.regimens, fn({_pid, regimen, time, _items, flag}) ->
|
||||
%{regimen: regimen,
|
||||
info: %{
|
||||
start_time: time,
|
||||
status: flag}}
|
||||
end)
|
||||
cs = case state.current_sequence do
|
||||
{_, sequence} -> sequence
|
||||
uh -> uh
|
||||
end
|
||||
%__MODULE__{ process_info: regimen_info_list,
|
||||
current_sequence: cs,
|
||||
sequence_log: state.sequence_log}
|
||||
end
|
||||
|
||||
end
|
||||
@moduledoc false
|
||||
|
||||
@typedoc """
|
||||
|
@ -58,12 +101,14 @@ defmodule Farmbot.Scheduler do
|
|||
case SafeStorage.read(__MODULE__) do
|
||||
{:ok, %State{} = last_state} ->
|
||||
Logger.debug("loading previous #{__MODULE__} state: #{inspect last_state}")
|
||||
Map.update!(last_state, :regimens, fn(old_regimens) ->
|
||||
new_state = Map.update!(last_state, :regimens, fn(old_regimens) ->
|
||||
Enum.map(old_regimens, fn({_,regimen, finished_items, time, _}) ->
|
||||
{:ok, pid} = Regimen.VM.start_link(regimen, finished_items, time)
|
||||
{pid,regimen, finished_items, time, :normal}
|
||||
end)
|
||||
end)
|
||||
save_and_update(new_state)
|
||||
new_state
|
||||
_ ->
|
||||
Logger.debug("starting new #{__MODULE__} state.")
|
||||
default_state
|
||||
|
@ -107,29 +152,15 @@ defmodule Farmbot.Scheduler do
|
|||
{:noreply, %State{state | current_sequence: nil}}
|
||||
end
|
||||
|
||||
def handle_call(:state, _from, state) do
|
||||
{:reply, state, state}
|
||||
end
|
||||
|
||||
# This strips out pids, tuples and whatnot for json status updates
|
||||
def handle_call(:jsonable, _from, state) do
|
||||
regimen_info_list = Enum.map(state.regimens, fn({_pid, regimen, time, _items, flag}) ->
|
||||
%{regimen: regimen,
|
||||
info: %{
|
||||
start_time: time,
|
||||
status: flag}}
|
||||
end)
|
||||
cs = case state.current_sequence do
|
||||
{_, sequence} -> sequence
|
||||
uh -> uh
|
||||
end
|
||||
jsonable =
|
||||
%{process_info: regimen_info_list,
|
||||
current_sequence: cs,
|
||||
sequence_log: state.sequence_log}
|
||||
def handle_call(:jsonable, state) do
|
||||
jsonable = State.Serializer.serialize(state)
|
||||
{:reply, jsonable, state}
|
||||
end
|
||||
|
||||
def handle_call(:state, _from, state) do
|
||||
{:reply, state, state}
|
||||
end
|
||||
|
||||
def handle_call({:add, {:sequence, sequence}}, _from, state) do
|
||||
{:reply, :ok, %State{state | sequence_log: state.sequence_log ++ [sequence]}}
|
||||
|
@ -150,7 +181,7 @@ defmodule Farmbot.Scheduler do
|
|||
{:ok, pid} = Regimen.VM.start_link(regimen, [], start_time)
|
||||
reg_tup = {pid, regimen, [], start_time, :normal}
|
||||
new_state = %State{state | regimens: current ++ [reg_tup]}
|
||||
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
|
||||
save_and_update(new_state)
|
||||
{:reply, :starting, new_state}
|
||||
|
||||
# If the regimen is in paused state.
|
||||
|
@ -169,16 +200,18 @@ defmodule Farmbot.Scheduler do
|
|||
# The Sequence finished. Cleanup if its still alive..
|
||||
def handle_info({:done, {:sequence, pid, _sequence}}, state) do
|
||||
GenServer.stop(pid, :normal)
|
||||
{:noreply, %State{state | current_sequence: nil}}
|
||||
new_state = %State{state | current_sequence: nil}
|
||||
save_and_update(new_state)
|
||||
{:noreply, new_state }
|
||||
end
|
||||
|
||||
# A regimen is ready to be stopped.
|
||||
def handle_info({:done, {:regimen, pid, regimen}}, state) do
|
||||
Logger.debug("Regimen: #{regimen.name} has finished.")
|
||||
GenServer.stop(pid, :normal)
|
||||
reg_tup = find_regimen(regimen, state.regimens)
|
||||
new_state = %State{state | regimens: state.regimens -- [reg_tup]}
|
||||
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
|
||||
save_and_update(new_state)
|
||||
GenServer.stop(pid, :normal)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
|
@ -196,7 +229,7 @@ defmodule Farmbot.Scheduler do
|
|||
fn({^pid, ^regimen, _old_items, ^start_time, _flag}) ->
|
||||
{pid, regimen, finished_items, start_time, flag}
|
||||
end)}
|
||||
SafeStorage.write(__MODULE__, :erlang.term_to_binary(new_state))
|
||||
save_and_update(new_state)
|
||||
{:noreply, new_state}
|
||||
is_nil(found) ->
|
||||
# Something is not good. try to clean up.
|
||||
|
@ -247,6 +280,13 @@ defmodule Farmbot.Scheduler do
|
|||
regimens: regimens}}
|
||||
end
|
||||
|
||||
@spec save_and_update(State.t) :: :ok
|
||||
def save_and_update(%State{} = state) do
|
||||
GenServer.cast(Farmbot.BotState,
|
||||
{:scheduler, State.Serializer.serialize(state)})
|
||||
SafeStorage.write(__MODULE__, :erlang.term_to_binary(state))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a regimen in the list.
|
||||
"""
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Farmbot.Supervisor do
|
|||
def init(%{target: target, compat_version: compat_version,
|
||||
version: version, env: env}) do
|
||||
children = [
|
||||
# worker(Farmbot.Logger, [[]], restart: :permanent),
|
||||
# Storage that needs to persist across reboots.
|
||||
worker(SafeStorage, [env], restart: :permanent),
|
||||
worker(SSH, [env], restart: :permanent),
|
||||
|
|
|
@ -56,6 +56,7 @@ defmodule RPC.MessageHandler do
|
|||
@doc """
|
||||
Shortcut for logging a message to the frontend.
|
||||
= Channel can be =
|
||||
| :ticker |
|
||||
| :error_ticker |
|
||||
| :error_toast |
|
||||
| :success_toast |
|
||||
|
@ -72,13 +73,9 @@ defmodule RPC.MessageHandler do
|
|||
|
||||
# This is what actually updates the rest of the world about farmbots status.
|
||||
def send_status do
|
||||
status =
|
||||
Map.merge(Farmbot.BotState.get_status,
|
||||
%{farm_scheduler: GenServer.call(Farmbot.Scheduler, :jsonable)})
|
||||
|
||||
m = %{id: nil,
|
||||
method: "status_update",
|
||||
params: [status] }
|
||||
@transport.emit(Poison.encode!(m))
|
||||
params: [Farmbot.BotState.get_status] }
|
||||
@transport.emit(Poison.encode!(m))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ defmodule JsonRpc.Parser do
|
|||
Poison.encode!(
|
||||
%{ id: nil,
|
||||
method: "log_message",
|
||||
params: [%{ status: Farmbot.BotState.get_status,
|
||||
params: [%{ status: %{location: Farmbot.BotState.get_current_pos}, # Shhhh
|
||||
time: :os.system_time(:seconds),
|
||||
message: message,
|
||||
channels: channels,
|
||||
|
|
4
mix.exs
4
mix.exs
|
@ -98,8 +98,8 @@ defmodule Farmbot.Mixfile do
|
|||
[
|
||||
{:nerves, "~> 0.3.0"},
|
||||
{:nerves_firmware_http, github: "nerves-project/nerves_firmware_http"},
|
||||
{:farmbot_configurator, github: "Farmbot/farmbot_configurator"}
|
||||
# {:farmbot_configurator, path: "../farmbot_configurator"}
|
||||
# {:farmbot_configurator, github: "Farmbot/farmbot_configurator"}
|
||||
{:farmbot_configurator, path: "../farmbot_configurator"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue