Store gpio registations in db, publish in bot state tree

This commit is contained in:
Connor Rigby 2017-11-29 17:55:08 -08:00 committed by Connor Rigby
parent 44b233dad8
commit 4ea3efaafd
9 changed files with 138 additions and 13 deletions

View file

@ -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)

View file

@ -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

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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