Store gpio registations in db, publish in bot state tree
This commit is contained in:
parent
44b233dad8
commit
4ea3efaafd
|
@ -105,6 +105,7 @@ defmodule Farmbot.BotState do
|
|||
location_data: %{
|
||||
position: %{x: -1, y: -1, z: -1}
|
||||
},
|
||||
gpio_registry: %{},
|
||||
pins: %{},
|
||||
configuration: %{},
|
||||
informational_settings: %{
|
||||
|
@ -347,6 +348,12 @@ defmodule Farmbot.BotState do
|
|||
do_handle(rest, state)
|
||||
end
|
||||
|
||||
# This one is special because it sends the _entire_ sub tree, not just
|
||||
# Parts of it.
|
||||
defp do_handle([{:gpio_registry, gpio_registry} | rest], state) do
|
||||
do_handle(rest, %{state | gpio_registry: gpio_registry})
|
||||
end
|
||||
|
||||
defp do_handle([{key, diff} | rest], state) do
|
||||
state = %{state | key => Map.merge(Map.get(state, key), diff)}
|
||||
do_handle(rest, state)
|
||||
|
|
|
@ -13,8 +13,8 @@ defmodule Farmbot.CeleryScript.AST.Node.RegisterGpio do
|
|||
case seq do
|
||||
nil -> {:error, "Could not find sequence by id: #{id}", env}
|
||||
seq ->
|
||||
Logger.busy 1, "Registering: #{pin_num} to sequence: #{seq.name}"
|
||||
case Farmbot.System.GPIO.register_sequence(pin_num, id) do
|
||||
Logger.busy 1, "Registering gpio: #{pin_num} to sequence: #{seq.name}"
|
||||
case Farmbot.System.GPIO.register_pin(pin_num, id) do
|
||||
:ok -> {:ok, env}
|
||||
{:error, reason} -> {:error, reason, env}
|
||||
end
|
||||
|
|
14
lib/farmbot/celery_script/ast/node/unregister_gpio.ex
Normal file
14
lib/farmbot/celery_script/ast/node/unregister_gpio.ex
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Farmbot.CeleryScript.AST.Node.UnregisterGpio do
|
||||
@moduledoc false
|
||||
use Farmbot.CeleryScript.AST.Node
|
||||
allow_args [:pin_number]
|
||||
use Farmbot.Logger
|
||||
|
||||
def execute(%{pin_number: pin_num}, _, env) do
|
||||
env = mutate_env(env)
|
||||
case Farmbot.System.GPIO.unregister_pin(pin_num)do
|
||||
:ok -> {:ok, env}
|
||||
{:error, reason} -> {:error, reason, env}
|
||||
end
|
||||
end
|
||||
end
|
20
lib/farmbot/system/config_storage/gpio_registry.ex
Normal file
20
lib/farmbot/system/config_storage/gpio_registry.ex
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule Farmbot.System.ConfigStorage.GpioRegistry do
|
||||
@moduledoc "Union between device (linux) pin and Farmbot Sequence."
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Farmbot.System.ConfigStorage.GpioRegistry
|
||||
|
||||
schema "gpio_registry" do
|
||||
field(:pin, :integer)
|
||||
field(:sequence_id, :integer)
|
||||
end
|
||||
|
||||
@required_fields [:pin, :sequence_id]
|
||||
|
||||
def changeset(%GpioRegistry{} = gpio, params \\ %{}) do
|
||||
gpio
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
||||
end
|
||||
end
|
|
@ -2,12 +2,19 @@ defmodule Farmbot.System.GPIO do
|
|||
@moduledoc "Handles GPIO inputs."
|
||||
use GenStage
|
||||
use Farmbot.Logger
|
||||
alias Farmbot.System.ConfigStorage
|
||||
alias ConfigStorage.GpioRegistry
|
||||
|
||||
@handler Application.get_env(:farmbot, :behaviour)[:gpio_handler]
|
||||
|
||||
@doc "Register a pin number to execute sequence."
|
||||
def register_sequence(pin_num, sequence_id) do
|
||||
GenStage.call(__MODULE__, {:register_sequence, pin_num, sequence_id})
|
||||
def register_pin(pin_num, sequence_id) do
|
||||
GenStage.call(__MODULE__, {:register_pin, pin_num, sequence_id})
|
||||
end
|
||||
|
||||
@doc "Unregister a sequence."
|
||||
def unregister_pin(sequence_id) do
|
||||
GenStage.call(__MODULE__, {:unregister_pin, sequence_id})
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -23,11 +30,23 @@ defmodule Farmbot.System.GPIO do
|
|||
def init([]) do
|
||||
case @handler.start_link() do
|
||||
{:ok, handler} ->
|
||||
{:producer_consumer, struct(State, [handler: handler]), subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher}
|
||||
all_gpios = ConfigStorage.all(GpioRegistry)
|
||||
state = initial_state(all_gpios, struct(State, [handler: handler]))
|
||||
Process.send_after(self(), :update_fb_state_tree, 10)
|
||||
{:producer_consumer, state, subscribe_to: [handler], dispatcher: GenStage.BroadcastDispatcher}
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
defp initial_state([], state), do: state
|
||||
|
||||
defp initial_state([%GpioRegistry{pin: pin, sequence_id: sequence_id} | rest], state) do
|
||||
case @handler.register_pin(pin) do
|
||||
:ok -> initial_state(rest, %{state | registered: Map.put(state.registered, pin, sequence_id)})
|
||||
_ -> initial_state(rest, state)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_events(pin_triggers, _from, state) do
|
||||
t = Enum.uniq(pin_triggers)
|
||||
for {:pin_trigger, pin} <- t do
|
||||
|
@ -42,10 +61,41 @@ defmodule Farmbot.System.GPIO do
|
|||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_call({:register_sequence, pin_num, sequence_id}, _from, state) do
|
||||
case @handler.register_pin(pin_num) do
|
||||
:ok -> {:reply, :ok, [], %{state | registered: Map.put(state.registered, pin_num, sequence_id)}}
|
||||
{:error, _} = err -> {:reply, err, [], state}
|
||||
def handle_info(:update_fb_state_tree, state) do
|
||||
{:noreply, [{:gpio_registry, state.registered}], state}
|
||||
end
|
||||
|
||||
def handle_call({:register_pin, pin_num, sequence_id}, _from, state) do
|
||||
case state.registered[pin_num] do
|
||||
nil ->
|
||||
case @handler.register_pin(pin_num) do
|
||||
:ok ->
|
||||
reg = struct(GpioRegistry, [pin: pin_num, sequence_id: sequence_id])
|
||||
ConfigStorage.insert!(reg)
|
||||
new_state = %{state | registered: Map.put(state.registered, pin_num, sequence_id)}
|
||||
{:reply, :ok, [{:gpio_registry, new_state.registered}], new_state}
|
||||
{:error, _} = err -> {:reply, err, [], state}
|
||||
end
|
||||
_ -> {:reply, {:error, :already_registered}, [], state}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def handle_call({:unregister_pin, pin_num}, _from, state) do
|
||||
case state.registered[pin_num] do
|
||||
nil -> {:reply, {:error, :unregistered}, [], state}
|
||||
sequence_id ->
|
||||
case @handler.unregister_pin(pin_num) do
|
||||
:ok ->
|
||||
import Ecto.Query
|
||||
case ConfigStorage.one(from g in GpioRegistry, where: g.pin == ^pin_num and g.sequence_id == ^sequence_id) do
|
||||
nil -> :ok
|
||||
obj -> ConfigStorage.delete!(obj)
|
||||
end
|
||||
new_state = %{state | registered: Map.delete(state.registered, pin_num)}
|
||||
{:reply, :ok, [{:gpio_registry, new_state.registered}], new_state}
|
||||
err -> {:reply, err, [], state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,4 +6,7 @@ defmodule Farmbot.System.GPIO.Handler do
|
|||
|
||||
@doc "Register a pin."
|
||||
@callback register_pin(integer) :: :ok | {:error, term}
|
||||
|
||||
@doc "Unregister a pin."
|
||||
@callback unregister_pin(integer) :: :ok | {:error, term}
|
||||
end
|
||||
|
|
|
@ -11,6 +11,10 @@ defmodule Farmbot.System.GPIO.StubHandler do
|
|||
GenStage.call(__MODULE__, {:register_pin, num})
|
||||
end
|
||||
|
||||
def unregister_pin(num) do
|
||||
GenStage.call(__MODULE__, {:unregister_pin, num})
|
||||
end
|
||||
|
||||
def start_link do
|
||||
GenStage.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
end
|
||||
|
@ -27,6 +31,10 @@ defmodule Farmbot.System.GPIO.StubHandler do
|
|||
{:reply, :ok, [], Map.put(state, num, :enabled)}
|
||||
end
|
||||
|
||||
def handle_call({:unregister_pin, num}, _from, state) do
|
||||
{:reply, :ok, [], Map.delete(state, num)}
|
||||
end
|
||||
|
||||
def handle_call({:test_fire, pin}, _from, state) do
|
||||
case state[pin] do
|
||||
nil -> {:reply, :error, [], state}
|
||||
|
|
|
@ -14,6 +14,10 @@ defmodule Farmbot.Target.GPIO.AleHandler do
|
|||
GenStage.call(__MODULE__, {:register_pin, num})
|
||||
end
|
||||
|
||||
def unregister_pin(num) do
|
||||
GenStage.call(__MODULE__, {:unregister_pin, num})
|
||||
end
|
||||
|
||||
# GenStage Callbacks
|
||||
|
||||
defmodule State do
|
||||
|
@ -23,7 +27,7 @@ defmodule Farmbot.Target.GPIO.AleHandler do
|
|||
|
||||
defmodule PinState do
|
||||
@moduledoc false
|
||||
defstruct [:pin, :state, :signal, :timer]
|
||||
defstruct [:pin, :state, :signal, :timer, :pid]
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
|
@ -35,15 +39,24 @@ defmodule Farmbot.Target.GPIO.AleHandler do
|
|||
end
|
||||
|
||||
def handle_call({:register_pin, num}, _from, state) do
|
||||
with {:ok, pin} <- GPIO.start_link(num, :input),
|
||||
:ok <- GPIO.set_int(pin, :rising) do
|
||||
{:reply, :ok, [], %{state | pins: Map.put(state.pins, num, struct(PinState, [pin: pin, state: nil, signal: :rising]))}}
|
||||
with {:ok, pid} <- GPIO.start_link(num, :input),
|
||||
:ok <- GPIO.set_int(pid, :rising) do
|
||||
{:reply, :ok, [], %{state | pins: Map.put(state.pins, num, struct(PinState, [pin: num, pid: pid, state: nil, signal: :rising]))}}
|
||||
else
|
||||
{:error, _} = err-> {:reply, err, [], state}
|
||||
err -> {:reply, {:error, err}, [], state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({:unregister_pin, num}, _from, state) do
|
||||
case state.pins[num] do
|
||||
nil -> {:reply, :ok, [], state}
|
||||
%PinState{pid: pid} ->
|
||||
GPIO.release(pid)
|
||||
{:reply, :ok, [], %{state | pins: Map.delete(state.pins, num)}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:gpio_interrupt, pin, :rising}, state) do
|
||||
pin_state = state.pins[pin]
|
||||
if pin_state.timer do
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Farmbot.System.ConfigStorage.Migrations.AddGpioRegistry do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:gpio_registry) do
|
||||
add(:pin, :integer)
|
||||
add(:sequence_id, :integer)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue