clean this up pls
parent
cf2c4784ae
commit
f19535b604
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -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"},
|
||||
|
|
|
@ -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"},
|
||||
|
|
Loading…
Reference in New Issue