clean this up pls

pull/363/head
Connor Rigby 2017-09-29 16:55:10 -07:00
parent cf2c4784ae
commit f19535b604
19 changed files with 155 additions and 934 deletions

View File

@ -1,134 +1,36 @@
defmodule Farmbot.BotState do
@moduledoc """
State tree of the bot.
"""
alias Farmbot.BotState.{
Configuration,
InformationalSettings,
Job,
LocationData,
McuParams,
Pin,
ProcessInfo
}
defstruct [
configuration: %Configuration{},
informational_settings: %InformationalSettings{},
location_data: %LocationData{},
mcu_params: %McuParams{},
process_info: %ProcessInfo{},
jobs: %{},
pins: %{},
user_env: %{},
]
@typedoc "Bot State"
@type t :: %__MODULE__{
informational_settings: InformationalSettings.t,
configuration: Configuration.t,
location_data: LocationData.t,
process_info: ProcessInfo.t,
mcu_params: McuParams.t,
jobs: %{optional(binary) => Job.t},
pins: %{optional(number) => Pin.t},
user_env: %{optional(binary) => binary}
}
@typedoc "Instance of this module."
@type state_server :: GenServer.server
@doc """
Subscribe to updates from the bot.
## Updates
Updates will come in randomly in the shape of
`{:bot_state, state}` where state is a BotState struct.
"""
@spec subscribe(state_server) :: :ok | {:error, :already_subscribed}
def subscribe(state_tracker) do
GenServer.call(state_tracker, :subscribe)
end
@doc """
Unsubscribes from the bot state tracker.
returns a boolean where true means the called pid was
actually subscribed and false means it was not actually subscribed.
"""
@spec unsubscribe(state_server) :: boolean
def unsubscribe(state_tracker) do
GenServer.call(state_tracker, :unsubscribe)
end
@doc "Forces a dispatch to all the clients."
@spec force_dispatch(state_server) :: :ok
def force_dispatch(state_server) do
GenServer.call(state_server, :force_dispatch)
end
## GenServer
use GenServer
use GenStage
require Logger
# these modules have seperate process backing them.
@update_mods [InformationalSettings, Configuration, LocationData, ProcessInfo, McuParams]
defstruct [
mcu_params: %{},
jobs: %{},
location_data: %{},
pins: %{},
configuration: %{},
informational_settings: %{},
user_env: %{},
process_info: %{}
]
defmodule PrivateState do
@moduledoc "State for the GenServer."
defstruct [
subscribers: [],
bot_state: %Farmbot.BotState{}
]
@typedoc "Private State."
@type t :: %__MODULE__{subscribers: [GenServer.server],
bot_state: Farmbot.BotState.t}
end
@doc "Start the Bot State Server."
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, [], opts)
def start_link(opts) do
GenStage.start_link(__MODULE__, [], opts)
end
def init([]) do
{:ok, %PrivateState{}}
{:producer_consumer, struct(__MODULE__), subscribe_to: [Farmbot.Firmware]}
end
# This is the signature of an update to a key in the bot's state.
# Only except updates from module backed by a process somewhere.
def handle_cast({:update, module, value}, priv) when module in @update_mods do
["Farmbot", "BotState", camel] = Module.split(module)
new_bot_state = %{priv.bot_state | :"#{Macro.underscore(camel)}" => value}
dispatch priv, new_bot_state
def handle_events(events, _from, state) do
state = do_handle(events, state)
{:noreply, [state], state}
end
def handle_call(:subscribe, {pid, _ref}, priv) do
# Checks if this pid is already subscribed.
if pid in priv.subscribers do
{:reply, {:error, :already_subscribed}, priv}
else
send pid, {:bot_state, priv.bot_state}
{:reply, :ok, %{priv | subscribers: [pid | priv.subscribers]}}
end
defp do_handle([], state), do: state
defp do_handle([{key, diff} | rest], state) do
state = %{state | key => Map.merge(Map.get(state, key), diff)}
do_handle(rest, state)
end
def handle_call(:unsubscribe, {pid, _ref}, %{subscribers: subs} = priv) do
actually_removed = pid in subs
{:reply, actually_removed, %{priv | subscribers: List.delete(subs, pid)}}
end
def handle_call(:force_dispatch, _from, priv) do
dispatch priv, priv.bot_state
{:reply, :ok, priv}
end
# Dispatches a new state to all the subscribers.
defp dispatch(%PrivateState{subscribers: subs} = priv, %__MODULE__{} = new) do
for sub <- subs do
send sub, {:bot_state, new}
end
{:noreply, %{priv | bot_state: new}}
end
end

View File

@ -1,25 +0,0 @@
defmodule Farmbot.BotState.Configuration do
@moduledoc "Externally Editable configuration data"
alias Farmbot.System.ConfigStorage, as: CS
defstruct [
os_auto_update: false,
first_party_farmware: true,
timezone: nil
]
@typedoc "Config data"
@type t :: %__MODULE__{
os_auto_update: boolean,
first_party_farmware: boolean,
timezone: binary | nil
}
use Farmbot.BotState.Lib.Partition
def save_state(%__MODULE__{} = pub) do
CS.update_config_value(:bool, "settings", "os_auto_update", pub.os_auto_update)
CS.update_config_value(:bool, "settings", "first_party_farmware", pub.first_party_farmware)
CS.update_config_value(:string, "settings", "timezone", pub.timezone)
end
end

View File

@ -1,65 +0,0 @@
defmodule Farmbot.BotState.InformationalSettings do
@moduledoc "Configuration data that should only be changed internally."
defmodule SyncStatus do
@moduledoc "Enum of all available statuses for the sync message."
@statuses [
:locked,
:maintenance,
:sync_error,
:sync_now,
:synced,
:syncing,
:unknown,
]
@typedoc "Status of the sync bar"
@type t :: :locked |
:maintenance |
:sync_error |
:sync_now |
:synced |
:syncing |
:unknown
def status(sts) when sts in @statuses do
sts
end
def status(unknown) when is_atom(unknown) or is_binary(unknown) do
raise "unknown sync status: #{unknown}"
end
end
@version Mix.Project.config[:version]
defstruct [
controller_version: @version,
firmware_version: :disconnected,
throttled: false,
private_ip: :disconnected,
sync_status: SyncStatus.status(:sync_now),
busy: true
]
@typedoc "Information Settings."
@type t :: %__MODULE__{
controller_version: Version.version,
firmware_version: :disconnected | Version.version,
throttled: boolean,
private_ip: :disconnected | Version.version,
sync_status: SyncStatus.t,
busy: boolean
}
use Farmbot.BotState.Lib.Partition
def set_busy(part, busy) do
GenServer.call(part, {:set_busy, busy})
end
def partition_call({:set_busy, busy}, _, state) do
{:reply, :ok, %{state | busy: busy}}
end
end

View File

@ -1,42 +0,0 @@
defmodule Farmbot.BotState.Job do
@moduledoc "Job that can be represented as a progress bar of some sort."
defmodule PercentProgress do
@moduledoc "Progress represented 0-100"
defstruct [
status: :working,
unit: :percent,
percent: 0
]
@typedoc "A Progress struct represented as a percentage"
@type t :: %__MODULE__{
status: Farmbot.BotState.Job.status,
unit: :percent,
percent: number
}
end
defmodule ByteProgress do
@moduledoc "Progress represented as a number of bytes."
defstruct [
status: :working,
unit: :bytes,
bytes: 0
]
@typedoc "A Progress struct represented by number of bytes."
@type t :: %__MODULE__{
status: Farmbot.BotState.Job.status,
unit: :bytes,
bytes: number
}
end
@typedoc "Job struct"
@type t :: PercentProgress.t | ByteProgress.t
@typedoc "Status of the job"
@type status :: :working | :complete | :error
end

View File

@ -1,160 +0,0 @@
defmodule Farmbot.BotState.Lib.Partition do
@moduledoc "Common functionality for parts of the bot state."
alias Farmbot.BotState
defmodule PrivateState do
@moduledoc "Internal state to a Partition."
@enforce_keys [:bot_state_tracker, :public]
defstruct [:bot_state_tracker, :public]
@typedoc "Public state."
@type public :: map
@typedoc "Private State."
@type t :: %__MODULE__{
bot_state_tracker: Farmbot.BotState.state_server,
public: public
}
end
@typedoc "Reply to a GenServer.call."
@type reply :: {:reply, term, PrivateState.public}
@typedoc "Noreply for GenServer.cast or GenServer.info."
@type noreply :: {:noreply, PrivateState.public}
@typedoc "Stop the Partition."
@type stop :: {:stop, term, PrivateState.public} | {:stop, term}
@doc "Start this Partition GenServer."
@callback start_link(BotState.server, GenServer.options) :: GenServer.on_start
@doc "optional callback called on init."
@callback partition_init(PrivateState.t) :: {:ok, PrivateState.t} | {:stop, term}
@doc "Optional callback for handle_call."
@callback partition_call(term, GenServer.from, PrivateState.public) :: reply | noreply | stop
@doc "Optional callback for handle_cast."
@callback partition_cast(term, PrivateState.public) :: noreply | stop
@doc "Optional callback for handle_info."
@callback partition_info(term, PrivateState.public) :: noreply | stop
@doc "Otional callback for saveing state."
@callback save_state(PrivateState.public) :: :ok | :error
@optional_callbacks [
partition_init: 1,
partition_call: 3,
partition_info: 2,
partition_cast: 2,
save_state: 1
]
@doc "Dispatches to the bot state tracker."
@spec dispatch(PrivateState.t) :: {:noreply, PrivateState.t}
def dispatch(private_state)
def dispatch(%PrivateState{} = priv) do
GenServer.cast(priv.bot_state_tracker, {:update, priv.public.__struct__, priv.public})
{:noreply, priv}
end
@doc "Dispatches to the bot state tracker, and replies with `reply`"
@spec dispatch(term, PrivateState.t) :: {:reply, term, PrivateState.t}
def dispatch(reply, private_state)
def dispatch(reply, %PrivateState{} = priv) do
GenServer.cast(priv.bot_state_tracker, {:update, priv.public.__struct__, priv.public})
{:reply, reply, priv}
end
@doc false
defmacro __using__(_opts) do
quote do
alias Farmbot.BotState.Lib.Partition
import Partition
@behaviour Partition
alias Partition.PrivateState
use GenServer
require Logger
@doc "Start the partition."
def start_link(bot_state_tracker, opts) do
GenServer.start_link(__MODULE__, bot_state_tracker, opts)
end
def init(bot_state_tracker) do
initial = %PrivateState{public: struct(__MODULE__),
bot_state_tracker: bot_state_tracker}
partition_init(initial)
end
def handle_call(call, from, %PrivateState{} = priv) do
case partition_call(call, from, priv.public) do
{:reply, reply, pub} -> dispatch reply, %{priv | public: pub}
{:noreply, pub} ->
save_public_data(pub)
dispatch %{priv | public: pub}
other -> other
end
end
def handle_cast(cast, %PrivateState{} = priv) do
case partition_cast(cast, priv.public) do
{:noreply, pub} ->
save_public_data(pub)
dispatch %{priv | public: pub}
other -> other
end
end
def handle_info(info, %PrivateState{} = priv) do
case partition_info(info, priv.public) do
{:noreply, pub} ->
save_public_data(pub)
dispatch %{priv | public: pub}
other -> other
end
end
@doc false
def partition_init(%PrivateState{} = priv), do: {:ok, priv}
@doc false
def partition_call(call, _from, public) do
Logger.error "Unhandled call: #{inspect call}"
{:stop, {:unhandled_call, call}, public}
end
@doc false
def partition_cast(cast, public) do
Logger.warn "Unhandled cast: #{inspect cast}"
{:noreply, public}
end
@doc false
def partition_info(info, public) do
Logger.warn "Unhandled info: #{inspect info}"
{:noreply, public}
end
defp save_public_data(pub) do
if function_exported?(__MODULE__, :save_state, 1) do
:ok = apply(__MODULE__, :save_state, [pub])
else
:ok
end
end
defoverridable [partition_init: 1,
partition_call: 3,
partition_cast: 2,
partition_info: 2,
]
end
end
end

View File

@ -1,73 +0,0 @@
defmodule Farmbot.BotState.LocationData do
@moduledoc "Data about the bot's location in space"
defmodule Vec3 do
@moduledoc "3 Position Vector used for locations"
defstruct [:x, :y, :z]
@typedoc "x position."
@type x :: number
@typedoc "y position."
@type y :: number
@typedoc "z position."
@type z :: number
@typedoc "3 Position vector used for location data"
@type t :: %__MODULE__{x: x , y: y , z: z }
@doc "Builds a new 3 position vector."
@spec new(x, y, z) :: t
def new(x, y, z), do: %__MODULE__{x: x, y: y, z: z}
end
defstruct [
position: Vec3.new(-1, -1, -1),
scaled_encoders: Vec3.new(-1, -1, -1),
raw_encoders: Vec3.new(-1, -1, -1),
end_stops: "-1-1-1-1-1-1"
]
@typedoc "Data about the bot's position."
@type t :: %__MODULE__{
position: Vec3.t,
scaled_encoders: Vec3.t,
raw_encoders: Vec3.t,
end_stops: binary
}
use Farmbot.BotState.Lib.Partition
def report_current_position(part, x, y, z) do
GenServer.call(part, {:report_current_position, Vec3.new(x,y,z)})
end
def report_encoder_position_scaled(part, x, y, z) do
GenServer.call(part, {:report_encoder_position_scaled, Vec3.new(x,y,z)})
end
def report_encoder_position_raw(part, x, y, z) do
GenServer.call(part, {:report_encoder_position_raw, Vec3.new(x,y,z)})
end
def report_end_stops(part, xa, xb, ya, yb, za, zb) do
GenServer.call(part, {:report_end_stops, "#{xa}#{xb}#{ya}#{yb}#{za}#{zb}"})
end
def partition_call({:report_current_position, pos}, _, state) do
{:reply, :ok, %{state | position: pos}}
end
def partition_call({:report_encoder_position_scaled, pos}, _, state) do
{:reply, :ok, %{state | scaled_encoders: pos}}
end
def partition_call({:report_encoder_position_raw, pos}, _, state) do
{:reply, :ok, %{state | raw_encoders: pos}}
end
def partition_call({:report_end_stops, stops}, _, state) do
{:reply, :ok, %{state | end_stops: stops}}
end
end

View File

@ -1,141 +0,0 @@
defmodule Farmbot.BotState.McuParams do
@moduledoc "Params for the firmware"
defstruct [
:encoder_enabled_x,
:encoder_enabled_y,
:encoder_enabled_z,
:encoder_invert_x,
:encoder_invert_y,
:encoder_invert_z,
:encoder_missed_steps_decay_x,
:encoder_missed_steps_decay_y,
:encoder_missed_steps_decay_z,
:encoder_missed_steps_max_x,
:encoder_missed_steps_max_y,
:encoder_missed_steps_max_z,
:encoder_scaling_x,
:encoder_scaling_y,
:encoder_scaling_z,
:encoder_type_x,
:encoder_type_y,
:encoder_type_z,
:encoder_use_for_pos_x,
:encoder_use_for_pos_y,
:encoder_use_for_pos_z,
:movement_axis_nr_steps_x,
:movement_axis_nr_steps_y,
:movement_axis_nr_steps_z,
:movement_enable_endpoints_x,
:movement_enable_endpoints_y,
:movement_enable_endpoints_z,
:movement_home_at_boot_x,
:movement_home_at_boot_y,
:movement_home_at_boot_z,
:movement_home_up_x,
:movement_home_up_y,
:movement_home_up_z,
:movement_invert_endpoints_x,
:movement_invert_endpoints_y,
:movement_invert_endpoints_z,
:movement_invert_motor_x,
:movement_invert_motor_y,
:movement_invert_motor_z,
:movement_keep_active_x,
:movement_keep_active_y,
:movement_keep_active_z,
:movement_max_spd_x,
:movement_max_spd_y,
:movement_max_spd_z,
:movement_min_spd_x,
:movement_min_spd_y,
:movement_min_spd_z,
:movement_secondary_motor_invert_x,
:movement_secondary_motor_x,
:movement_steps_acc_dec_x,
:movement_steps_acc_dec_y,
:movement_steps_acc_dec_z,
:movement_stop_at_home_x,
:movement_stop_at_home_y,
:movement_stop_at_home_z,
:movement_stop_at_max_x,
:movement_stop_at_max_y,
:movement_stop_at_max_z,
:movement_timeout_x,
:movement_timeout_y,
:movement_timeout_z,
:param_mov_nr_retry,
:param_e_stop_on_mov_err,
:param_version,
]
@typedoc "Params for the FW."
@type t :: %__MODULE__{
encoder_enabled_x: number,
encoder_enabled_y: number,
encoder_enabled_z: number,
encoder_invert_x: number,
encoder_invert_y: number,
encoder_invert_z: number,
encoder_missed_steps_decay_x: number,
encoder_missed_steps_decay_y: number,
encoder_missed_steps_decay_z: number,
encoder_missed_steps_max_x: number,
encoder_missed_steps_max_y: number,
encoder_missed_steps_max_z: number,
encoder_scaling_x: number,
encoder_scaling_y: number,
encoder_scaling_z: number,
encoder_type_x: number,
encoder_type_y: number,
encoder_type_z: number,
encoder_use_for_pos_x: number,
encoder_use_for_pos_y: number,
encoder_use_for_pos_z: number,
movement_axis_nr_steps_x: number,
movement_axis_nr_steps_y: number,
movement_axis_nr_steps_z: number,
movement_enable_endpoints_x: number,
movement_enable_endpoints_y: number,
movement_enable_endpoints_z: number,
movement_home_at_boot_x: number,
movement_home_at_boot_y: number,
movement_home_at_boot_z: number,
movement_home_up_x: number,
movement_home_up_y: number,
movement_home_up_z: number,
movement_invert_endpoints_x: number,
movement_invert_endpoints_y: number,
movement_invert_endpoints_z: number,
movement_invert_motor_x: number,
movement_invert_motor_y: number,
movement_invert_motor_z: number,
movement_keep_active_x: number,
movement_keep_active_y: number,
movement_keep_active_z: number,
movement_max_spd_x: number,
movement_max_spd_y: number,
movement_max_spd_z: number,
movement_min_spd_x: number,
movement_min_spd_y: number,
movement_min_spd_z: number,
movement_secondary_motor_invert_x: number,
movement_secondary_motor_x: number,
movement_steps_acc_dec_x: number,
movement_steps_acc_dec_y: number,
movement_steps_acc_dec_z: number,
movement_stop_at_home_x: number,
movement_stop_at_home_y: number,
movement_stop_at_home_z: number,
movement_stop_at_max_x: number,
movement_stop_at_max_y: number,
movement_stop_at_max_z: number,
movement_timeout_x: number,
movement_timeout_y: number,
movement_timeout_z: number,
param_mov_nr_retry: number,
param_e_stop_on_mov_err: number,
param_version: number,
}
use Farmbot.BotState.Lib.Partition
end

View File

@ -1,23 +0,0 @@
defmodule Farmbot.BotState.Pin do
@moduledoc "State of a pin."
@typedoc false
@type digital :: 1
@digital 1
@typedoc false
@type pwm :: 0
@pwm 0
@enforce_keys [:mode, :value]
defstruct [:mode, :value]
@typedoc "Pin."
@type t :: %__MODULE__{mode: digital | pwm, value: number}
@doc "Digital pin mode."
def digital, do: @digital
@doc "Pwm pin mode."
def pwm, do: @pwm
end

View File

@ -1,22 +0,0 @@
defmodule Farmbot.BotState.ProcessInfo do
@moduledoc "Info about long running processes."
defstruct [
farmwares: %{}
]
@typedoc "State of the Process Info server."
@type t :: %__MODULE__{
farmwares: %{optional(Farmware.name) => Farmware.t}
}
use Farmbot.BotState.Lib.Partition
def update_farmwares(part, farmwares) do
GenServer.cast(part, {:update_farmwares, farmwares})
end
def partition_cast({:update_farmwares, farmwares}, state) do
{:noreply, %{state | farmwares: farmwares}}
end
end

View File

@ -1,37 +1,16 @@
defmodule Farmbot.BotState.Supervisor do
@moduledoc "Supervises BotState stuff."
use Supervisor
alias Farmbot.BotState
alias Farmbot.BotState.{
InformationalSettings, Configuration, LocationData, ProcessInfo, McuParams,
Transport
}
alias Farmbot.Firmware, as: FW
@doc "Start the BotState stack."
def start_link(token, opts \\ []) do
def start_link(token, opts) do
Supervisor.start_link(__MODULE__, token, opts)
end
def init(token) do
def init(_token) do
children = [
# BotState parts.
worker(BotState, [[name: BotState]]),
worker(InformationalSettings, [BotState, [name: InformationalSettings]]),
worker(Configuration, [BotState, [name: Configuration]]),
worker(LocationData, [BotState, [name: LocationData]]),
worker(ProcessInfo, [BotState, [name: ProcessInfo]]),
worker(McuParams, [BotState, [name: McuParams]]),
# Transport part.
supervisor(Transport.Supervisor, [token, BotState, [name: Transport.Supervisor]]),
# Firmware part.
supervisor(FW.Supervisor, [BotState, InformationalSettings, Configuration, LocationData, McuParams, [name: FW.Supervisor]]),
supervisor(Farmbot.Firmware.Supervisor, [[name: Farmbot.Firmware.Supervisor]]),
worker(Farmbot.BotState, [[name: Farmbot.BotState]]),
worker(Farmbot.BotState.Transport.GenMQTT, [[name: Farmbot.BotState.Transport.GenMQTT]])
]
# We set one_for_all here, since all of these link to `BotState`
supervise(children, [strategy: :one_for_all])
supervise(children, strategy: :one_for_one)
end
end

View File

@ -1,42 +0,0 @@
defmodule Farmbot.BotState.Transport do
@moduledoc """
Serializes Farmbot's state to be send out to any subscribed transports.
"""
@doc "Start a transport."
@callback start_link(Farmbot.Bootstrap.Authorization.token, Farmbot.BotState.state_server) :: GenServer.on_start()
@doc "Log a message."
@callback log(Farmbot.Log.t) :: :ok
@doc "Emit a message."
@callback emit(Farmbot.CeleryScript.Ast.t) :: :ok
@error_msg """
Could not find :transport configuration.
config.exs should have:
config: :farmbot, :transport, [
# any transport modules here.
]
"""
@doc "All transports."
def transports do
Application.get_env(:farmbot, :transport) || raise @error_msg
end
@doc "Emit a message over all transports."
def emit(%Farmbot.CeleryScript.Ast{} = msg) do
for transport <- transports() do
:ok = transport.emit(msg)
end
end
@doc "Log a message over all transports."
def log(%Farmbot.Log{} = log) do
for transport <- transports() do
:ok = transport.log(log)
end
end
end

View File

@ -1,107 +1,29 @@
defmodule Farmbot.BotState.Transport.GenMqtt do
@moduledoc "Default MQTT Transport."
@behaviour Farmbot.BotState.Transport
use GenMQTT
defmodule Farmbot.BotState.Transport.GenMQTT do
use GenStage
require Logger
# Callback function
def emit(msg), do: emit(__MODULE__, msg)
@doc "Emit a message on `client`."
def emit(client, msg) do
GenMQTT.cast(client, {:emit, msg})
def start_link(opts) do
GenStage.start_link(__MODULE__, [], opts)
end
# Callback function
def log(log), do: log(__MODULE__, log)
@doc "Log a message on `client`."
def log(client, log) do
GenMQTT.cast(client, {:log, log})
def init([]) do
{:ok, pid} = Farmbot.Transport.GenMQTTClient.start_link()
{:consumer, {pid, nil}, subscribe_to: [Farmbot.BotState]}
end
# Callback function.
def start_link(bin_token, bot_state_tracker, name \\ __MODULE__) do
token = Farmbot.Jwt.decode!(bin_token)
GenMQTT.start_link(__MODULE__, [token.bot, bot_state_tracker], build_opts(bin_token, token, name))
def handle_events(events, _, {pid, state}) do
new_state = blah(events, state)
if new_state != state do
send pid, {:bot_state, new_state}
Logger.info "State: #{inspect new_state}"
else
# Logger.info "no change"
end
{:noreply, [], {pid, new_state}}
end
## Server Implementation.
defmodule State do
@moduledoc false
defstruct [bot: nil, connected: false]
def blah([], state), do: state
def blah([event | rest], _state) do
blah(rest, event)
end
def init([bot, bot_state_tracker]) do
:ok = Farmbot.BotState.subscribe(bot_state_tracker)
{:ok, %State{bot: bot}}
end
def on_connect(state) do
GenMQTT.subscribe(self(), [{bot_topic(state.bot), 0}])
Logger.info ">> Connected!"
{:ok, %{state | connected: true}}
end
def on_connect_error(:invalid_credentials, state) do
msg = """
Failed to authenticate with the message broker!
This is likely a problem with your server/broker configuration.
"""
Logger.error ">> #{msg}"
Farmbot.System.factory_reset(msg)
{:ok, state}
end
def on_connect_error(reason, state) do
Logger.error ">> Failed to connect to mqtt: #{inspect reason}"
{:ok, state}
end
def on_publish(["bot", _bot, "from_clients"], msg, state) do
Logger.warn "not implemented yet: #{inspect msg}"
{:ok, state}
end
def handle_info(_, %{connected: false} = state), do: {:ok, state}
def handle_info({:bot_state, bs}, state) do
Logger.info "Got bot state update"
json = Poison.encode!(bs)
GenMQTT.publish(self(), status_topic(state.bot), json, 0, false)
{:noreply, state}
end
def handle_cast(_, %{connected: false} = state), do: {:noreply, state}
def handle_cast({:log, msg}, state) do
json = Poison.encode! msg
GenMQTT.publish(self(), log_topic(state.bot), json, 0, false)
{:noreply, state}
end
def handle_cast({:emit, msg}, state) do
json = Poison.encode! msg
GenMQTT.publish(self(), frontend_topic(state.bot), json, 0, false)
{:noreply, state}
end
defp frontend_topic(bot), do: "bot/#{bot}/from_device"
defp bot_topic(bot), do: "bot/#{bot}/from_clients"
defp status_topic(bot), do: "bot/#{bot}/status"
defp log_topic(bot), do: "bot/#{bot}/logs"
defp build_opts(bin_token, %{mqtt: mqtt, bot: bot}, name) do
[
name: name,
reconnect_timeout: 10_000,
username: bot,
password: bin_token,
timeout: 10_000,
host: mqtt
]
end
end

View File

@ -0,0 +1,59 @@
defmodule Farmbot.Transport.GenMQTTClient do
use GenMQTT
require Logger
@token "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhZG1pbi5jb20iLCJpYXQiOjE1MDY3MDQ5MzUsImp0aSI6IjViYzlmYTcyLTc2NDYtNDJhMi04ZjM5LTRlMjJhNGExOTBhMCIsImlzcyI6Ii8vMTkyLjE2OC44Ni4xMTA6MzAwMCIsImV4cCI6MTUxMDE2MDkzNSwibXF0dCI6IjE5Mi4xNjguODYuMTEwIiwib3NfdXBkYXRlX3NlcnZlciI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvZmFybWJvdC9mYXJtYm90X29zL3JlbGVhc2VzL2xhdGVzdCIsImZ3X3VwZGF0ZV9zZXJ2ZXIiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL0Zhcm1ib3QvZmFybWJvdC1hcmR1aW5vLWZpcm13YXJlL3JlbGVhc2VzL2xhdGVzdCIsImJvdCI6ImRldmljZV8yIn0.nYQ9QohmEiLM5OeNuTk8a_9XVUALTjM05K6ki2hsGJw6S0Rc2i0ZpYHobK8du3pZFG-0-CC0YSHx8H9B4QPhs0HpF6gQIGg-3SPCtHoZz3abaQ_mocyVAyGv8CSsDQXVOkZIIeOzxN9yhhtRMTbJinjfPvmZccQ3qcR-_rOmoJETHB22IPUzE1oW0KdhQFwzK9m87TJk56vyyQeBAIyvHMHzgSwPQlzj9uOV3Id8nwjTBJ9fZ8q0737A3wK2Girv506l_7kk-5z_6LqI0yPV-6IBtCeyVtyWiOo7Feq1CFlQWIdQH7VdsvxphwarDqFHjj7Nofz8NeFjlIb9YGCH9w"
@device "device_2"
def start_link do
GenMQTT.start_link(__MODULE__, [], [
reconnect_timeout: 10_000,
username: @device,
password: @token,
timeout: 10_000,
host: "localhost"
])
end
def init(_) do
{:ok, %{connected: false}}
end
def on_connect_error(:invalid_credentials, state) do
msg = """
Failed to authenticate with the message broker!
This is likely a problem with your server/broker configuration.
"""
Logger.error ">> #{msg}"
Farmbot.System.factory_reset(msg)
{:ok, state}
end
def on_connect_error(reason, state) do
Logger.error ">> Failed to connect to mqtt: #{inspect reason}"
{:ok, state}
end
def on_connect(state) do
GenMQTT.subscribe(self(), [{bot_topic(@device), 0}])
Logger.info ">> Connected!"
{:ok, %{state | connected: true}}
end
def on_publish(["bot", _bot, "from_clients"], msg, state) do
Logger.warn "not implemented yet: #{inspect msg}"
{:ok, state}
end
def handle_info({:bot_state, bs}, state) do
Logger.info "Got bot state update"
json = Poison.encode!(bs)
GenMQTT.publish(self(), status_topic(@device), json, 0, false)
{:noreply, state}
end
defp frontend_topic(bot), do: "bot/#{bot}/from_device"
defp bot_topic(bot), do: "bot/#{bot}/from_clients"
defp status_topic(bot), do: "bot/#{bot}/status"
defp log_topic(bot), do: "bot/#{bot}/logs"
end

View File

@ -1,22 +0,0 @@
defmodule Farmbot.BotState.Transport.Supervisor do
@moduledoc """
Supervises services that communicate with the outside world.
"""
use Supervisor
import Farmbot.BotState.Transport, only: [transports: 0]
@doc "Start the Transport Supervisor."
def start_link(token, bot_state_tracker, opts \\ []) do
Supervisor.start_link(__MODULE__, [token, bot_state_tracker], opts)
end
def init([token, bot_state_tracker]) do
# Start workers that consume the bots state, and push it somewhere else.
children = Enum.map(transports(), fn(transport) ->
worker(transport, [token, bot_state_tracker])
end)
opts = [strategy: :one_for_one]
supervise(children, opts)
end
end

View File

@ -1,76 +1,38 @@
defmodule Farmbot.Firmware do
@moduledoc "Allows communication with the firmware."
use GenServer
use GenStage
require Logger
alias Farmbot.BotState
alias BotState.{
InformationalSettings,
LocationData
}
@doc "Public API for handling a gcode."
def handle_gcode(firmware, code), do: GenServer.call(firmware, {:handle_gcode, code})
def start_link(bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod, opts) do
GenServer.start_link(__MODULE__, [bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod], opts)
def start_link(opts) do
GenStage.start_link(__MODULE__, [], opts)
end
defmodule State do
defstruct [
:bot_state,
:informational_settings,
:configuration,
:location_data,
:mcu_params,
:handler_mod,
:handler
]
def init([]) do
{:producer_consumer, [], subscribe_to: [Farmbot.Firmware.UartHandler]}
end
def init([bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod]) do
{:ok, handler} = handler_mod.start_link(self(), name: handler_mod)
Process.link(handler)
s = %State{
bot_state: bot_state,
informational_settings: informational_settings,
configuration: configuration,
location_data: location_data,
mcu_params: mcu_params,
handler_mod: handler_mod,
handler: handler
}
{:ok, s}
def handle_events(gcodes, _from, state) do
{:noreply, handle_gcodes(gcodes), state}
end
def handle_call({:handle_gcode, :idle}, _, state) do
reply = InformationalSettings.set_busy(state.informational_settings, false)
{:reply, reply, state}
def handle_gcodes(codes, acc \\ [])
def handle_gcodes([], acc), do: Enum.reverse(acc)
def handle_gcodes([code | rest], acc) do
res = handle_gcode(code)
if res do
handle_gcodes(rest, [res | acc])
else
handle_gcodes(rest, acc)
end
end
def handle_call({:handle_gcode, {:report_current_position, x, y, z}}, _, state) do
reply = LocationData.report_current_position(state.location_data, x, y, z)
{:reply, reply, state}
def handle_gcode({:report_current_position, x, y, z}) do
{:location_data, %{position: %{x: x, y: y, z: z}}}
end
def handle_call({:handle_gcode, {:report_encoder_position_scaled, x, y, z}}, _, state) do
reply = LocationData.report_encoder_position_scaled(state.location_data, x, y, z)
{:reply, reply, state}
end
def handle_call({:handle_gcode, {:report_encoder_position_raw, x, y, z}}, _, state) do
reply = LocationData.report_encoder_position_raw(state.location_data, x, y, z)
{:reply, reply, state}
end
def handle_call({:handle_gcode, {:report_end_stops, xa, xb, ya, yb, za, zb}}, _, state) do
reply = LocationData.report_end_stops(state.location_data, xa, xb, ya, yb, za, zb)
{:reply, reply, state}
end
def handle_call({:handle_gcode, code}, _, state) do
Logger.warn "Got misc gcode: #{inspect code}"
{:reply, {:error, :unhandled}, state}
def handle_gcode(code) do
# Logger.warn "unhandled code: #{inspect code}"
nil
end
end

View File

@ -5,14 +5,15 @@ defmodule Farmbot.Firmware.Supervisor do
@error_msg "Please configure a Firmware handler."
@doc "Start the Firmware Supervisor."
def start_link(bot_state, informational_settings, configuration, location_data, mcu_params, opts \\ []) do
Supervisor.start_link(__MODULE__, [bot_state, informational_settings, configuration, location_data, mcu_params], opts)
def start_link(opts \\ []) do
Supervisor.start_link(__MODULE__, [], opts)
end
def init([bot_state, informational_settings, configuration, location_data, mcu_params]) do
def init([]) do
handler_mod = Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise @error_msg
children = [
worker(Farmbot.Firmware, [bot_state, informational_settings, configuration, location_data, mcu_params, handler_mod, [name: Farmbot.Firmware]])
worker(Farmbot.Firmware.UartHandler, [[name: Farmbot.Firmware.UartHandler]]),
worker(Farmbot.Firmware, [[name: Farmbot.Firmware]])
]
opts = [strategy: :one_for_one]
supervise(children, opts)

View File

@ -3,7 +3,7 @@ defmodule Farmbot.Firmware.UartHandler do
Handles communication between farmbot and uart devices
"""
use GenServer
use GenStage
alias Nerves.UART
alias Farmbot.Firmware.Gcode.Parser
require Logger
@ -12,12 +12,12 @@ defmodule Farmbot.Firmware.UartHandler do
Writes a string to the uart line
"""
def write(handler, string) do
GenServer.call(handler, {:write, string}, :infinity)
GenStage.call(handler, {:write, string}, :infinity)
end
@doc "Starts a UART GenServer"
def start_link(firmware, opts) do
GenServer.start_link(__MODULE__, firmware, opts)
def start_link(opts) do
GenStage.start_link(__MODULE__, [], opts)
end
## Private
@ -25,17 +25,17 @@ defmodule Farmbot.Firmware.UartHandler do
defmodule State do
@moduledoc false
defstruct [
:firmware,
:nerves
:nerves,
:codes
]
end
def init(firmware) do
def init([]) do
tty = Application.get_env(:farmbot, :uart_handler)[:tty] || raise "Please configure uart handler!"
{:ok, nerves} = UART.start_link()
Process.link(nerves)
case open_tty(nerves, tty) do
:ok -> {:ok, %State{firmware: firmware, nerves: nerves}}
:ok -> {:producer, %State{nerves: nerves, codes: []}}
err -> {:stop, err, :no_state}
end
end
@ -65,16 +65,24 @@ defmodule Farmbot.Firmware.UartHandler do
def handle_info({:nerves_uart, _, bin}, state) do
case Parser.parse_code(bin) do
{:unhandled_gcode, code_str} ->
Logger.warn "Got unhandled code: #{code_str}"
# Logger.warn "Got unhandled code: #{code_str}"
{:noreply, [], state}
{_q, gcode} ->
_reply = Farmbot.Firmware.handle_gcode(state.firmware, gcode)
do_dispatch([gcode | state.codes], state)
end
{:noreply, state}
end
def handle_call({:write, stuff}, _from, state) do
UART.write(state.nerves, stuff)
{:reply, :ok, state}
{:reply, :ok, [], state}
end
def handle_demand(_amnt, state) do
do_dispatch(state.codes, state)
end
defp do_dispatch(codes, state) do
{:noreply, Enum.reverse(codes), %{state | codes: []}}
end
end

View File

@ -70,6 +70,8 @@ defmodule Farmbot.Mixfile do
{:gen_mqtt, "~> 0.3.1"},
{:vmq_commons, "1.0.0", manager: :rebar3},
{:gen_stage, "~> 0.12"},
{:poison, "~> 3.0"},
{:ex_json_schema, "~> 0.5.3"},
{:rsa, "~> 0.0.1"},

View File

@ -19,6 +19,7 @@
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"},
"gen_mqtt": {:hex, :gen_mqtt, "0.3.1", "6ce6af7c2bcb125d5b4125c67c5ab1f29bcec2638236509bcc6abf510a6661ed", [], [{:vmq_commons, "1.0.0", [hex: :vmq_commons, repo: "hexpm", optional: false]}], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.12.2", "e0e347cbb1ceb5f4e68a526aec4d64b54ad721f0a8b30aa9d28e0ad749419cbb", [], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"},
"hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},