From 4ea3efaafdf0a70a13070125ff3b1c4a714aa19d Mon Sep 17 00:00:00 2001 From: Connor Rigby Date: Wed, 29 Nov 2017 17:55:08 -0800 Subject: [PATCH] Store gpio registations in db, publish in bot state tree --- lib/farmbot/bot_state/bot_state.ex | 7 ++ .../celery_script/ast/node/register_gpio.ex | 4 +- .../celery_script/ast/node/unregister_gpio.ex | 14 ++++ .../system/config_storage/gpio_registry.ex | 20 ++++++ lib/farmbot/system/gpio/gpio.ex | 64 +++++++++++++++++-- lib/farmbot/system/gpio/handler.ex | 3 + lib/farmbot/system/gpio/stub_handler.ex | 8 +++ nerves/target/gpio/ale_handler.ex | 21 ++++-- .../20171130004447_add_gpio_registry.exs | 10 +++ 9 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 lib/farmbot/celery_script/ast/node/unregister_gpio.ex create mode 100644 lib/farmbot/system/config_storage/gpio_registry.ex create mode 100644 priv/config_storage/migrations/20171130004447_add_gpio_registry.exs diff --git a/lib/farmbot/bot_state/bot_state.ex b/lib/farmbot/bot_state/bot_state.ex index d8eb482e..de9363a3 100644 --- a/lib/farmbot/bot_state/bot_state.ex +++ b/lib/farmbot/bot_state/bot_state.ex @@ -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) diff --git a/lib/farmbot/celery_script/ast/node/register_gpio.ex b/lib/farmbot/celery_script/ast/node/register_gpio.ex index 82435190..c93dacbb 100644 --- a/lib/farmbot/celery_script/ast/node/register_gpio.ex +++ b/lib/farmbot/celery_script/ast/node/register_gpio.ex @@ -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 diff --git a/lib/farmbot/celery_script/ast/node/unregister_gpio.ex b/lib/farmbot/celery_script/ast/node/unregister_gpio.ex new file mode 100644 index 00000000..49ec4ccf --- /dev/null +++ b/lib/farmbot/celery_script/ast/node/unregister_gpio.ex @@ -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 diff --git a/lib/farmbot/system/config_storage/gpio_registry.ex b/lib/farmbot/system/config_storage/gpio_registry.ex new file mode 100644 index 00000000..b3f866fd --- /dev/null +++ b/lib/farmbot/system/config_storage/gpio_registry.ex @@ -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 diff --git a/lib/farmbot/system/gpio/gpio.ex b/lib/farmbot/system/gpio/gpio.ex index bc2e015f..df8ae65b 100644 --- a/lib/farmbot/system/gpio/gpio.ex +++ b/lib/farmbot/system/gpio/gpio.ex @@ -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 diff --git a/lib/farmbot/system/gpio/handler.ex b/lib/farmbot/system/gpio/handler.ex index 27e6263c..f5216041 100644 --- a/lib/farmbot/system/gpio/handler.ex +++ b/lib/farmbot/system/gpio/handler.ex @@ -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 diff --git a/lib/farmbot/system/gpio/stub_handler.ex b/lib/farmbot/system/gpio/stub_handler.ex index b721b7bf..65f31c96 100644 --- a/lib/farmbot/system/gpio/stub_handler.ex +++ b/lib/farmbot/system/gpio/stub_handler.ex @@ -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} diff --git a/nerves/target/gpio/ale_handler.ex b/nerves/target/gpio/ale_handler.ex index 95011007..1dcc6d6f 100644 --- a/nerves/target/gpio/ale_handler.ex +++ b/nerves/target/gpio/ale_handler.ex @@ -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 diff --git a/priv/config_storage/migrations/20171130004447_add_gpio_registry.exs b/priv/config_storage/migrations/20171130004447_add_gpio_registry.exs new file mode 100644 index 00000000..ad912852 --- /dev/null +++ b/priv/config_storage/migrations/20171130004447_add_gpio_registry.exs @@ -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