From a40af03ff5e578863b10fac8bd803638d1e14b9f Mon Sep 17 00:00:00 2001 From: James Harton Date: Sun, 12 Jan 2020 16:55:54 +1300 Subject: [PATCH] Move `release/1` from `Conn` to it's own protocol. --- lib/wafer/conn.ex | 15 ++++---- lib/wafer/drivers/circuits/gpio.ex | 9 ++++- lib/wafer/drivers/circuits/i2c.ex | 12 +++++- lib/wafer/drivers/circuits/spi.ex | 9 ++++- lib/wafer/drivers/elixir_ale/gpio.ex | 9 ++++- lib/wafer/drivers/elixir_ale/i2c.ex | 12 +++++- lib/wafer/drivers/elixir_ale/spi.ex | 9 ++++- lib/wafer/drivers/fake.ex | 7 ++-- lib/wafer/release.ex | 55 +++++++++++++++++++++++++++ test/drivers/circuits_gpio_test.exs | 5 ++- test/drivers/circuits_i2c_test.exs | 6 +-- test/drivers/circuits_spi_test.exs | 6 +-- test/drivers/elixir_ale_gpio_test.exs | 5 ++- test/drivers/elixir_ale_i2c_test.exs | 6 +-- test/drivers/elixir_ale_spi_test.exs | 6 +-- 15 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 lib/wafer/release.ex diff --git a/lib/wafer/conn.ex b/lib/wafer/conn.ex index 4305133..7a56eb3 100644 --- a/lib/wafer/conn.ex +++ b/lib/wafer/conn.ex @@ -23,12 +23,18 @@ defmodule Wafer.Conn do with {:ok, conn} <- Driver.acquire(bus_name: bus, address: address), do: {:ok, %HTS221{conn: conn}} end - - def release(%HTS221{conn: conn}), do: Driver.release(conn) end ``` """ + defmacro __using__(_env) do + quote do + @behaviour Wafer.Conn + end + + Protocol.assert_impl!(Wafer.Release, __MODULE__) + end + @type t :: any @type options :: [option] @type option :: {atom, any} @@ -37,9 +43,4 @@ defmodule Wafer.Conn do Acquire a connection to a peripheral using the provided driver. """ @callback acquire(options) :: {:ok, t} | {:error, reason :: any} - - @doc """ - Release all resources associated with this connection. - """ - @callback release(module) :: :ok | {:error, reason :: any} end diff --git a/lib/wafer/drivers/circuits/gpio.ex b/lib/wafer/drivers/circuits/gpio.ex index 46014d5..e2b9288 100644 --- a/lib/wafer/drivers/circuits/gpio.ex +++ b/lib/wafer/drivers/circuits/gpio.ex @@ -36,14 +36,19 @@ defmodule Wafer.Driver.Circuits.GPIO do {:error, reason} -> {:error, reason} end end +end + +defimpl Wafer.Release, for: Wafer.Driver.Circuits.GPIO do + alias Wafer.Driver.Circuits.GPIO.Wrapper + alias Wafer.Driver.Circuits.GPIO @doc """ Release all resources related to this GPIO pin connection. Note that other connections may still be using the pin. """ - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{ref: ref} = _conn) when is_reference(ref), do: Wrapper.close(ref) + @spec release(GPIO.t()) :: :ok | {:error, reason :: any} + def release(%GPIO{ref: ref} = _conn) when is_reference(ref), do: Wrapper.close(ref) end defimpl Wafer.GPIO, for: Wafer.Driver.Circuits.GPIO do diff --git a/lib/wafer/drivers/circuits/i2c.ex b/lib/wafer/drivers/circuits/i2c.ex index f5c2905..fbf88c5 100644 --- a/lib/wafer/drivers/circuits/i2c.ex +++ b/lib/wafer/drivers/circuits/i2c.ex @@ -38,9 +38,17 @@ defmodule Wafer.Driver.Circuits.I2C do {:error, reason} end end +end - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{ref: ref} = _conn), do: Wrapper.close(ref) +defimpl Wafer.Release, for: Wafer.Driver.Circuits.I2C do + alias Wafer.Driver.Circuits.I2C.Wrapper + alias Wafer.Driver.Circuits.I2C + + @doc """ + Release all resources associated with this device. + """ + @spec release(I2C.t()) :: :ok | {:error, reason :: any} + def release(%I2C{ref: ref} = _conn), do: Wrapper.close(ref) end defimpl Wafer.Chip, for: Wafer.Driver.Circuits.I2C do diff --git a/lib/wafer/drivers/circuits/spi.ex b/lib/wafer/drivers/circuits/spi.ex index 22c42ad..21ae99c 100644 --- a/lib/wafer/drivers/circuits/spi.ex +++ b/lib/wafer/drivers/circuits/spi.ex @@ -34,12 +34,17 @@ defmodule Wafer.Driver.Circuits.SPI do {:error, reason} -> {:error, reason} end end +end + +defimpl Wafer.Release, for: Wafer.Driver.Circuits.SPI do + alias Wafer.Driver.Circuits.SPI.Wrapper + alias Wafer.Driver.Circuits.SPI @doc """ Close the SPI bus connection. """ - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{ref: ref} = _conn) when is_reference(ref), do: Wrapper.close(ref) + @spec release(SPI.t()) :: :ok | {:error, reason :: any} + def release(%SPI{ref: ref} = _conn) when is_reference(ref), do: Wrapper.close(ref) end defimpl Wafer.SPI, for: Wafer.Driver.Circuits.SPI do diff --git a/lib/wafer/drivers/elixir_ale/gpio.ex b/lib/wafer/drivers/elixir_ale/gpio.ex index 94e9da8..e52124b 100644 --- a/lib/wafer/drivers/elixir_ale/gpio.ex +++ b/lib/wafer/drivers/elixir_ale/gpio.ex @@ -35,14 +35,19 @@ defmodule Wafer.Driver.ElixirALE.GPIO do {:error, reason} -> {:error, reason} end end +end + +defimpl Wafer.Release, for: Wafer.Driver.ElixirALE.GPIO do + alias Wafer.Driver.ElixirALE.GPIO.Wrapper + alias Wafer.Driver.ElixirALE.GPIO @doc """ Release all resources related to this GPIO pin connection. Note that other connections may still be using the pin. """ - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{pid: pid} = _conn), do: Wrapper.release(pid) + @spec release(GPIO.t()) :: :ok | {:error, reason :: any} + def release(%GPIO{pid: pid} = _conn), do: Wrapper.release(pid) end defimpl Wafer.GPIO, for: Wafer.Driver.ElixirALE.GPIO do diff --git a/lib/wafer/drivers/elixir_ale/i2c.ex b/lib/wafer/drivers/elixir_ale/i2c.ex index ca7edb9..271a578 100644 --- a/lib/wafer/drivers/elixir_ale/i2c.ex +++ b/lib/wafer/drivers/elixir_ale/i2c.ex @@ -38,9 +38,17 @@ defmodule Wafer.Driver.ElixirALE.I2C do {:error, reason} end end +end - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{pid: pid} = _conn) when is_pid(pid), do: Wrapper.release(pid) +defimpl Wafer.Release, for: Wafer.Driver.ElixirALE.I2C do + alias Wafer.Driver.ElixirALE.I2C.Wrapper + alias Wafer.Driver.ElixirALE.I2C + + @doc """ + Release all resources associated with this I2C device. + """ + @spec release(I2C.t()) :: :ok | {:error, reason :: any} + def release(%I2C{pid: pid} = _conn) when is_pid(pid), do: Wrapper.release(pid) end defimpl Wafer.Chip, for: Wafer.Driver.ElixirALE.I2C do diff --git a/lib/wafer/drivers/elixir_ale/spi.ex b/lib/wafer/drivers/elixir_ale/spi.ex index 3022709..d00c918 100644 --- a/lib/wafer/drivers/elixir_ale/spi.ex +++ b/lib/wafer/drivers/elixir_ale/spi.ex @@ -35,12 +35,17 @@ defmodule Wafer.Driver.ElixirALE.SPI do {:error, reason} -> {:error, reason} end end +end + +defimpl Wafer.Release, for: Wafer.Driver.ElixirALE.SPI do + alias Wafer.Driver.ElixirALE.SPI.Wrapper + alias Wafer.Driver.ElixirALE.SPI @doc """ Close the SPI bus connection. """ - @spec release(t) :: :ok | {:error, reason :: any} - def release(%__MODULE__{pid: pid} = _conn) when is_pid(pid), do: Wrapper.release(pid) + @spec release(SPI.t()) :: :ok | {:error, reason :: any} + def release(%SPI{pid: pid} = _conn) when is_pid(pid), do: Wrapper.release(pid) end defimpl Wafer.SPI, for: Wafer.Driver.ElixirALE.SPI do diff --git a/lib/wafer/drivers/fake.ex b/lib/wafer/drivers/fake.ex index 0fcd602..fb98b6f 100644 --- a/lib/wafer/drivers/fake.ex +++ b/lib/wafer/drivers/fake.ex @@ -22,9 +22,6 @@ defmodule Wafer.Driver.Fake do {:ok, %__MODULE__{opts: opts}} end - @impl Wafer.Conn - def release(%__MODULE__{}), do: :ok - defp emit_warning do :wafer |> Application.get_env(__MODULE__) @@ -32,6 +29,10 @@ defmodule Wafer.Driver.Fake do end end +defimpl Wafer.Release, for: Wafer.Driver.Fake do + def release(%Wafer.Driver.Fake{}), do: :ok +end + defimpl Wafer.Chip, for: Wafer.Driver.Fake do import Wafer.Guards diff --git a/lib/wafer/release.ex b/lib/wafer/release.ex new file mode 100644 index 0000000..77dd796 --- /dev/null +++ b/lib/wafer/release.ex @@ -0,0 +1,55 @@ +defprotocol Wafer.Release do + alias Wafer.Conn + + @moduledoc """ + A protocol for releasing connections. The opposite of `Conn`'s `acquire/1`. + + ## Deriving + + If you're implementing your own `Conn` type that simply delegates to one of + the lower level drivers that you can derive this protocol automatically: + + ```elixir + defstruct MyConn do + @derive Wafer.Release + defstruct [:conn] + end + ``` + + If your type uses a key other than `conn` for the inner connection you can specify it while deriving: + + ```elixir + defstruct MyConn do + @derive {Wafer.Release, key: :pin_conn} + defstruct [:pin_conn] + end + ``` + """ + + @doc """ + Release all resources associated with the connection. Usually in preparation + for shutdown. + """ + @spec release(Conn.t()) :: :ok + def release(conn) +end + +defimpl Wafer.Release, for: Any do + defmacro __deriving__(module, struct, options) do + key = Keyword.get(options, :key, :conn) + + unless Map.has_key?(struct, key) do + raise( + "Unable to derive `Wafer.Release` for `#{module}`: key `#{inspect(key)}` not present in struct." + ) + end + + quote do + defimpl Wafer.Release, for: unquote(module) do + def release(%{unquote(key) => inner_conn}), do: Wafer.Release.release(inner_conn) + end + end + end + + def release(unknown), do: {:error, "`Wafer.Release` not implemented for #{inspect(unknown)}"} +end diff --git a/test/drivers/circuits_gpio_test.exs b/test/drivers/circuits_gpio_test.exs index 9f3a169..43e42d9 100644 --- a/test/drivers/circuits_gpio_test.exs +++ b/test/drivers/circuits_gpio_test.exs @@ -5,6 +5,7 @@ defmodule WaferDriverCircuits.GPIOTest do alias Wafer.Driver.Circuits.GPIO.Dispatcher, as: Dispatcher alias Wafer.Driver.Circuits.GPIO.Wrapper alias Wafer.GPIO, as: GPIO + alias Wafer.Release, as: Release @moduledoc false describe "acquire/1" do @@ -25,7 +26,7 @@ defmodule WaferDriverCircuits.GPIOTest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the pin" do conn = conn() @@ -35,7 +36,7 @@ defmodule WaferDriverCircuits.GPIOTest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end diff --git a/test/drivers/circuits_i2c_test.exs b/test/drivers/circuits_i2c_test.exs index f0f45d3..4dfc845 100644 --- a/test/drivers/circuits_i2c_test.exs +++ b/test/drivers/circuits_i2c_test.exs @@ -4,7 +4,7 @@ defmodule WaferCircuits.I2CTest do alias Wafer.Chip alias Wafer.Driver.Circuits.I2C, as: Subject alias Wafer.Driver.Circuits.I2C.Wrapper - alias Wafer.I2C + alias Wafer.{Release, I2C} @moduledoc false describe "acquire/1" do @@ -72,7 +72,7 @@ defmodule WaferCircuits.I2CTest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the bus connection" do conn = conn() @@ -82,7 +82,7 @@ defmodule WaferCircuits.I2CTest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end diff --git a/test/drivers/circuits_spi_test.exs b/test/drivers/circuits_spi_test.exs index 3778501..94fb255 100644 --- a/test/drivers/circuits_spi_test.exs +++ b/test/drivers/circuits_spi_test.exs @@ -3,7 +3,7 @@ defmodule WaferCircuits.SPITest do use Mimic alias Wafer.Driver.Circuits.SPI, as: Subject alias Wafer.Driver.Circuits.SPI.Wrapper - alias Wafer.SPI + alias Wafer.{Release, SPI} @moduledoc false describe "acquire/1" do @@ -23,7 +23,7 @@ defmodule WaferCircuits.SPITest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the bus connection" do conn = conn() @@ -33,7 +33,7 @@ defmodule WaferCircuits.SPITest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end diff --git a/test/drivers/elixir_ale_gpio_test.exs b/test/drivers/elixir_ale_gpio_test.exs index d0bf3e1..8828a2c 100644 --- a/test/drivers/elixir_ale_gpio_test.exs +++ b/test/drivers/elixir_ale_gpio_test.exs @@ -5,6 +5,7 @@ defmodule WaferDriverElixirALE.GPIOTest do alias Wafer.Driver.ElixirALE.GPIO.Dispatcher, as: Dispatcher alias Wafer.Driver.ElixirALE.GPIO.Wrapper alias Wafer.GPIO, as: GPIO + alias Wafer.Release, as: Release @moduledoc false describe "acquire/1" do @@ -25,7 +26,7 @@ defmodule WaferDriverElixirALE.GPIOTest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the pin" do conn = conn() @@ -35,7 +36,7 @@ defmodule WaferDriverElixirALE.GPIOTest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end diff --git a/test/drivers/elixir_ale_i2c_test.exs b/test/drivers/elixir_ale_i2c_test.exs index c518ebd..e2b9336 100644 --- a/test/drivers/elixir_ale_i2c_test.exs +++ b/test/drivers/elixir_ale_i2c_test.exs @@ -4,7 +4,7 @@ defmodule WaferElixirALE.I2CTest do alias Wafer.Chip alias Wafer.Driver.ElixirALE.I2C, as: Subject alias Wafer.Driver.ElixirALE.I2C.Wrapper - alias Wafer.I2C + alias Wafer.{Release, I2C} @moduledoc false describe "acquire/1" do @@ -75,7 +75,7 @@ defmodule WaferElixirALE.I2CTest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the bus connection" do conn = conn() @@ -85,7 +85,7 @@ defmodule WaferElixirALE.I2CTest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end diff --git a/test/drivers/elixir_ale_spi_test.exs b/test/drivers/elixir_ale_spi_test.exs index 09e748e..be9613c 100644 --- a/test/drivers/elixir_ale_spi_test.exs +++ b/test/drivers/elixir_ale_spi_test.exs @@ -3,7 +3,7 @@ defmodule WaferElixirALE.SPITest do use Mimic alias Wafer.Driver.ElixirALE.SPI, as: Subject alias Wafer.Driver.ElixirALE.SPI.Wrapper - alias Wafer.SPI + alias Wafer.{Release, SPI} @moduledoc false describe "acquire/1" do @@ -24,7 +24,7 @@ defmodule WaferElixirALE.SPITest do end end - describe "release/1" do + describe "Release.release/1" do test "closes the bus connection" do conn = conn() @@ -34,7 +34,7 @@ defmodule WaferElixirALE.SPITest do :ok end) - assert :ok = Subject.release(conn) + assert :ok = Release.release(conn) end end