feat!: Remove ElixirALE support. (#67)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #67
Co-authored-by: James Harton <james@harton.nz>
Co-committed-by: James Harton <james@harton.nz>
This commit is contained in:
James Harton 2024-04-24 14:20:57 +12:00 committed by James Harton
parent 26b34fb83e
commit 6123ac8493
25 changed files with 23 additions and 1026 deletions

View file

@ -8,7 +8,7 @@ Wafer is an OTP application that assists with writing drivers for peripherals us
Wafer provides Elixir protocols for interacting with device registers and dealing with GPIO, so that you can use directly connected hardware GPIO pins or GPIO expanders such as the [MCP23008](https://www.microchip.com/wwwproducts/en/MCP23008) or the [CD74HC595](http://www.ti.com/product/CD74HC595) SPI shift register.
Wafer implements the [GPIO](https://hexdocs.pm/wafer/Wafer.GPIOProto.html) and [Chip](https://hexdocs.pm/wafer/Wafer.Chip.html) protocols for [ElixirALE](https://hex.pm/packages/elixir_ale)'s GPIO and I2C drivers, [Circuits.GPIO](https://hex.pm/packages/circuits_gpio) and [Circuits.I2C](https://hex.pm/packages/circuits_i2c). Implementing it for SPI should also be trivial, I just don't have any SPI devices to test with at the moment.
Wafer implements the [GPIO](https://hexdocs.pm/wafer/Wafer.GPIOProto.html) and [Chip](https://hexdocs.pm/wafer/Wafer.Chip.html) protocols for [Circuits.GPIO](https://hex.pm/packages/circuits_gpio) and [Circuits.I2C](https://hex.pm/packages/circuits_i2c). Implementing it for SPI should also be trivial, I just don't have any SPI devices to test with at the moment.
Documentation for the main branch can always be found [here](https://docs.harton.nz/james/wafer/).
@ -112,10 +112,10 @@ end
## Running the tests
I've included stub implementations of the parts of `ElixirALE` and `Circuits`
that are interacted with by this project, so the tests should run and pass on
machines without physical hardware interfaces. If you have a Raspberry Pi with
a Pi Sense Hat connected you can run the tests with the `SENSE_HAT_PRESENT=true`
I've included stub implementations of the parts of `Circuits` that are
interacted with by this project, so the tests should run and pass on machines
without physical hardware interfaces. If you have a Raspberry Pi with a Pi Sense
Hat connected you can run the tests with the `SENSE_HAT_PRESENT=true`
environment variable set and it will perform integration tests with two of the
sensors on this device.

View file

@ -5,8 +5,5 @@ defmodule Wafer do
This library doesn't do much on it's own, it is used to help with some of the
repetitive tasks of writing drivers for hardware peripherals such as I2C and
SPI connected sensors.
Wafer works with both ElixirALE and Circuits. As such it's up to you to
define which dependency you're using.
"""
end

View file

@ -9,7 +9,6 @@ defmodule Wafer.Application do
def start(_type, _args) do
children = [
{Registry, [keys: :duplicate, name: Wafer.InterruptRegistry]},
Wafer.Driver.ElixirALE.GPIO.Dispatcher,
Wafer.Driver.Circuits.GPIO.Dispatcher
]

View file

@ -52,7 +52,7 @@ defprotocol Wafer.Chip do
## Example
iex> {:ok, conn} = ElixirALE.I2C.acquire(bus: "i2c-1", address: 0x68)
iex> {:ok, conn} = Circuits.I2C.acquire(bus: "i2c-1", address: 0x68)
...> Chip.read_register(conn, 0, 1)
{:ok, <<0>>}
"""
@ -71,7 +71,7 @@ defprotocol Wafer.Chip do
## Example
iex> {:ok, conn} = ElixirALE.I2C.acquire(bus: "i2c", address: 0x68)
iex> {:ok, conn} = Circuits.I2C.acquire(bus: "i2c", address: 0x68)
...> Chip.write_register(conn, 0, <<0>>)
{:ok, conn}
"""
@ -88,7 +88,7 @@ defprotocol Wafer.Chip do
- `conn` a type which implements the `Wafer.Conn` behaviour.
- `register_address` the address of the register to swap.
- `new_data` the data to write to the regsiter.
- `new_data` the data to write to the register.
## Returns
@ -96,7 +96,7 @@ defprotocol Wafer.Chip do
## Example
iex> {:ok, conn} = ElixirALE.I2C.acquire(bus: "i2c", address: 0x68)
iex> {:ok, conn} = Circuits.I2C.acquire(bus: "i2c", address: 0x68)
...> Chip.swap_register(conn, 0, <<1>>)
{:ok, <<0>>, conn}
"""

View file

@ -5,13 +5,13 @@ defmodule Wafer.Driver.Circuits.GPIO.Wrapper do
defdelegate close(gpio), to: Circuits.GPIO
@compile {:no_warn_undefined, Circuits.GPIO}
defdelegate info(), to: Circuits.GPIO
defdelegate backend_info(gpio \\ nil), to: Circuits.GPIO
@compile {:no_warn_undefined, Circuits.GPIO}
defdelegate open(pin_number, pin_direction, options \\ []), to: Circuits.GPIO
@compile {:no_warn_undefined, Circuits.GPIO}
defdelegate pin(gpio), to: Circuits.GPIO
defdelegate identifiers(gpio), to: Circuits.GPIO
@compile {:no_warn_undefined, Circuits.GPIO}
defdelegate read(gpio), to: Circuits.GPIO

View file

@ -1,84 +0,0 @@
defmodule Wafer.Driver.ElixirALE.GPIO do
defstruct ~w[direction pid pin]a
@behaviour Wafer.Conn
alias Wafer.Driver.ElixirALE.GPIO.Wrapper
alias Wafer.GPIO
import Wafer.Guards
@moduledoc """
A connection to a native GPIO pin via ElixirALE's GPIO driver.
Implements the `Wafer.Conn` behaviour as well as the `Wafer.GPIO` protocol.
"""
@type t :: %__MODULE__{pid: pid}
@type options :: [option]
@type option :: {:pin, non_neg_integer} | {:direction, GPIO.pin_direction()} | {:force, boolean}
@doc """
Acquire a connection to the specified GPIO pin using the ElixirALE GPIO driver.
## Options
- `:pin` (required) - the integer pin number. Hardware dependent.
- `:direction` - either `:in` or `:out`. Defaults to `:out`.
"""
@spec acquire(options) :: {:ok, t} | {:error, reason :: any}
def acquire(opts) when is_list(opts) do
with {:ok, pin} when is_pin_number(pin) <- Keyword.fetch(opts, :pin),
direction when direction in [:in, :out] <- Keyword.get(opts, :direction, :out),
direction <- String.to_atom("#{direction}put"),
{:ok, pid} <- Wrapper.start_link(pin, direction, Keyword.drop(opts, ~w[pin direction]a)) do
{:ok, %__MODULE__{pid: pid, pin: pin, direction: direction}}
else
:error -> {:error, "ElixirALE.GPIO requires a `pin` option."}
{: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(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
alias Wafer.Driver.ElixirALE.GPIO.Wrapper
alias Wafer.Driver.ElixirALE.GPIO.Dispatcher
import Wafer.Guards
def read(%{pid: pid} = _conn) do
case Wrapper.read(pid) do
value when is_pin_value(value) -> {:ok, value}
{:error, reason} -> {:error, reason}
other -> {:error, "Invalid response from driver: #{inspect(other)}"}
end
end
def write(%{pid: pid} = conn, value) when is_pid(pid) and is_pin_value(value) do
with :ok <- Wrapper.write(pid, value), do: {:ok, conn}
end
def direction(_conn, _direction), do: {:error, :not_supported}
def enable_interrupt(conn, pin_condition, metadata \\ nil),
do: Dispatcher.enable(conn, pin_condition, metadata)
def disable_interrupt(conn, pin_condition),
do: Dispatcher.disable(conn, pin_condition)
def pull_mode(_conn, _pull_mode), do: {:error, :not_supported}
end
defimpl Wafer.DeviceID, for: Wafer.Driver.ElixirALE.GPIO do
def id(%{pin: pin}), do: {Wafer.Driver.ElixirALE.GPIO, pin}
end

View file

@ -1,65 +0,0 @@
defmodule Wafer.Driver.ElixirALE.GPIO.Dispatcher do
use GenServer
alias __MODULE__
alias Wafer.Driver.ElixirALE.GPIO.Wrapper
alias Wafer.{Conn, GPIO, InterruptRegistry}
@allowed_pin_conditions ~w[rising falling both]a
@moduledoc """
This module implements a simple dispatcher for GPIO interrupts when using
`ElixirALE`.
"""
@doc false
def start_link(opts),
do: GenServer.start_link(__MODULE__, [opts], name: Dispatcher)
@doc """
Enable interrupts for this connection using the specified pin_condition.
"""
@spec enable(Conn.t(), GPIO.pin_condition(), any) :: {:ok, Conn.t()} | {:error, reason :: any}
def enable(%{pin: pin} = conn, pin_condition, metadata \\ nil)
when pin_condition in @allowed_pin_conditions do
with {:ok, conn} <- GenServer.call(Dispatcher, {:enable, conn, pin_condition}),
:ok <- InterruptRegistry.subscribe(key(pin), pin_condition, conn, metadata) do
{:ok, conn}
end
end
@doc """
Disable interrupts for this connection on the specified pin_condition.
"""
@spec disable(Conn.t(), GPIO.pin_condition()) :: {:ok, Conn.t()} | {:error, reason :: any}
def disable(conn, pin_condition) when pin_condition in @allowed_pin_conditions,
do: GenServer.call(Dispatcher, {:disable, conn, pin_condition})
@impl true
def init(_opts) do
{:ok, %{}}
end
@impl true
def handle_call({:enable, %{pid: pid} = conn, pin_condition}, _from, state)
when pin_condition in @allowed_pin_conditions do
case Wrapper.set_int(pid, pin_condition) do
:ok -> {:reply, {:ok, conn}, state}
{:error, reason} -> {:reply, {:error, reason}, state}
end
end
def handle_call({:disable, %{pin: pin} = conn, pin_condition}, _from, state)
when pin_condition in @allowed_pin_conditions do
:ok = InterruptRegistry.unsubscribe(key(pin), pin_condition, conn)
{:reply, {:ok, conn}, state}
end
@impl true
def handle_info({:gpio_interrupt, pin, condition}, state)
when condition in @allowed_pin_conditions do
{:ok, _} = InterruptRegistry.publish(key(pin), condition)
{:noreply, state}
end
defp key(pin), do: {__MODULE__, pin}
end

View file

@ -1,27 +0,0 @@
defmodule Wafer.Driver.ElixirALE.GPIO.Wrapper do
@moduledoc false
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate child_spec(arg), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate init(list), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate pin(pid), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate read(pid), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate release(pid), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate set_int(pid, direction), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate start_link(pin, pin_direction, opts \\ []), to: ElixirALE.GPIO
@compile {:no_warn_undefined, ElixirALE.GPIO}
defdelegate write(pid, value), to: ElixirALE.GPIO
end

View file

@ -1,124 +0,0 @@
defmodule Wafer.Driver.ElixirALE.I2C do
defstruct ~w[address bus pid]a
alias Wafer.Driver.ElixirALE.I2C.Wrapper
alias Wafer.I2C
import Wafer.Guards
@moduledoc """
A connection to a chip via ElixirALE's I2C driver.
Implements the `Wafer.Conn` behaviour as well as the `Wafer.Chip` and `Wafer.I2C` protocols.
"""
@type t :: %__MODULE__{address: I2C.address(), bus: binary, pid: pid}
@type options :: [option]
@type option :: {:bus_name, binary} | {:address, I2C.address()}
@doc """
Acquire a connection to a peripheral using the ElixirALE I2C driver on the
specified bus and address.
"""
@spec acquire(options) :: {:ok, t} | {:error, reason :: any}
def acquire(opts) when is_list(opts) do
with {:ok, bus} when is_binary(bus) <- Keyword.fetch(opts, :bus_name),
{:ok, address} when is_i2c_address(address) <- Keyword.fetch(opts, :address),
{:ok, pid} <- Wrapper.start_link(bus, address),
devices when is_list(devices) <- Wrapper.detect_devices(pid),
true <- Keyword.get(opts, :force, false) || Enum.member?(devices, address) do
{:ok, %__MODULE__{bus: bus, address: address, pid: pid}}
else
false ->
{:error, "No device detected at address. Pass `force: true` to override."}
:error ->
{:error, "ElixirALE.I2C requires both `bus_name` and `address` options."}
{:error, reason} ->
{:error, reason}
end
end
end
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
alias Wafer.Driver.ElixirALE.I2C.Wrapper
import Wafer.Guards
def read_register(%{pid: pid}, register_address, bytes)
when is_pid(pid) and is_register_address(register_address) and is_byte_size(bytes) do
case Wrapper.write_read(pid, <<register_address>>, bytes) do
data when is_binary(data) and byte_size(data) == bytes -> {:ok, data}
{:error, reason} -> {:error, reason}
other -> {:error, "Invalid response from driver: #{inspect(other)}"}
end
end
def read_register(_conn, _register_address, _bytes), do: {:error, "Invalid argument"}
def write_register(%{pid: pid} = conn, register_address, data)
when is_pid(pid) and is_register_address(register_address) and is_binary(data) do
with :ok <- Wrapper.write(pid, <<register_address, data::binary>>), do: {:ok, conn}
end
def write_register(_conn, _register_address, _data), do: {:error, "Invalid argument"}
def swap_register(conn, register_address, data)
when is_register_address(register_address) and is_binary(data) do
with {:ok, old_data} <- read_register(conn, register_address, byte_size(data)),
{:ok, conn} <- write_register(conn, register_address, data) do
{:ok, old_data, conn}
end
end
def swap_register(_conn, _register_address, _data), do: {:error, "Invalid argument"}
end
defimpl Wafer.I2C, for: Wafer.Driver.ElixirALE.I2C do
import Wafer.Guards
alias Wafer.Driver.ElixirALE.I2C.Wrapper
def read(%{pid: pid}, bytes, options \\ [])
when is_pid(pid) and is_byte_size(bytes) and is_list(options) do
case Wrapper.read(pid, bytes) do
data when is_binary(data) and byte_size(data) == bytes -> {:ok, data}
{:error, reason} -> {:error, reason}
other -> {:error, "Invalid response from driver: #{inspect(other)}"}
end
end
def write(%{pid: pid} = conn, data, options \\ [])
when is_pid(pid) and is_binary(data) and is_list(options) do
with :ok <- Wrapper.write(pid, data), do: {:ok, conn}
end
def write_read(%{pid: pid} = conn, data, bytes, options \\ [])
when is_pid(pid) and is_binary(data) and is_byte_size(bytes) and is_list(options) do
case Wrapper.write_read(pid, data, bytes) do
data when is_binary(data) and byte_size(data) == bytes -> {:ok, data, conn}
{:error, reason} -> {:error, reason}
other -> {:error, "Invalid response from driver: #{inspect(other)}"}
end
end
def detect_devices(%{pid: pid}) do
case Wrapper.detect_devices(pid) do
devices when is_list(devices) -> {:ok, devices}
{:error, reason} -> {:error, reason}
end
end
end
defimpl Wafer.DeviceID, for: Wafer.Driver.ElixirALE.I2C do
def id(%{address: address, bus: bus}), do: {Wafer.Driver.ElixirALE.I2C, bus, address}
end

View file

@ -1,39 +0,0 @@
defmodule Wafer.Driver.ElixirALE.I2C.Wrapper do
@moduledoc false
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate child_spec(arg), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate detect_devices(pid_or_devname), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate device_names(), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate init(list), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate read(pid, count), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate read_device(pid, address, count), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate release(pid), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate start_link(devname, address, opts \\ []), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate write(pid, data), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate write_device(pid, address, data), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate write_read(pid, write_data, read_count), to: ElixirALE.I2C
@compile {:no_warn_undefined, ElixirALE.I2C}
defdelegate write_read_device(pid, address, write_data, read_count), to: ElixirALE.I2C
end

View file

@ -1,70 +0,0 @@
defmodule Wafer.Driver.ElixirALE.SPI do
defstruct ~w[bus pid]a
@behaviour Wafer.Conn
alias Wafer.Driver.ElixirALE.SPI.Wrapper
@moduledoc """
A connection to a chip via ElixirALE's SPI driver.
Implements the `Wafer.Conn` behaviour as well as the `Wafer.SPI` protocol.
"""
@type t :: %__MODULE__{bus: binary, pid: pid}
@type options :: [option | driver_option]
@type option :: {:bus_name, binary}
# These options are passed unchanged to the underlying driver.
@type driver_option ::
{:mode, 0..3}
| {:bits_per_word, 0..16}
| {:speed_hz, pos_integer}
| {:delay_us, non_neg_integer}
@doc """
Acquire a connection to a peripheral using the ElixirALE' SPI driver on the
specified bus and address.
"""
@spec acquire(options) :: {:ok, t} | {:error, reason :: any}
def acquire(opts) when is_list(opts) do
with {:ok, bus} when is_binary(bus) <- Keyword.fetch(opts, :bus_name),
{:ok, pid} when is_pid(pid) <-
Wrapper.start_link(bus, Keyword.delete(opts, :bus_name), []) do
{:ok, %__MODULE__{bus: bus, pid: pid}}
else
:error -> {:error, "ElixirALE.SPI requires a `bus_name` option"}
{: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(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
alias Wafer.Driver.ElixirALE.SPI.Wrapper
def transfer(%{pid: pid} = conn, data) when is_pid(pid) and is_binary(data) do
case Wrapper.transfer(pid, data) do
read_data when is_binary(read_data) and byte_size(read_data) == byte_size(data) ->
{:ok, read_data, conn}
{:error, reason} ->
{:error, reason}
other ->
{:error, "Invalid response from driver: #{inspect(other)}"}
end
end
end
defimpl Wafer.DeviceID, for: Wafer.Driver.ElixirALE.SPI do
def id(%{bus: bus}), do: {Wafer.Driver.ElixirALE.SPI, bus}
end

View file

@ -1,21 +0,0 @@
defmodule Wafer.Driver.ElixirALE.SPI.Wrapper do
@moduledoc false
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate child_spec(arg), to: ElixirALE.SPI
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate device_names(), to: ElixirALE.SPI
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate init(arg), to: ElixirALE.SPI
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate release(pid), to: ElixirALE.SPI
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate start_link(devname, spi_opts \\ [], opts \\ []), to: ElixirALE.SPI
@compile {:no_warn_undefined, ElixirALE.SPI}
defdelegate transfer(pid, data), to: ElixirALE.SPI
end

View file

@ -68,13 +68,12 @@ defprotocol Wafer.GPIO do
Interrupts will be sent to the calling process as messages in the form of
`{:interrupt, Conn.t(), pin_condition, metadata | nil}`.
## Implementors note
## Implementers note
`Wafer` starts it's own `Registry` named `Wafer.InterruptRegistry` which you
can use to publish your interrupts to using the above format. The registry
key is set as follows: `{PublishingModule, pin, pin_condition}`. You can see
examples in the `Circuits.GPIO.Dispatcher` and `ElixirALE.GPIO.Dispatcher`
modules.
examples in the `Circuits.GPIO.Dispatcher` module.
"""
@spec enable_interrupt(Conn.t(), pin_condition, any) ::
{:ok, Conn.t()} | {:error, reason :: any}

View file

@ -6,8 +6,8 @@ defprotocol Wafer.I2C do
want to use the `Chip` protocol for working with registers, but this is
provided for consistency's sake.
This API is extremely similar to the `ElixirALE.I2C` and `Circuits.I2C` APIs,
except that it takes a `Conn` which implements `I2C` as an argument.
This API is extremely similar to the `Circuits.I2C` APIs, except that it takes
a `Conn` which implements `I2C` as an argument.
## Deriving

View file

@ -7,9 +7,9 @@ defmodule Wafer.InterruptRegistry do
This module provides Wafer's interrupt registry. This allows multiple
subscribers to be subscribed to interrupts from many different pins.
It is used by `Driver.Circuits.GPIO.Dispatcher` and
`Driver.ElixirALE.GPIO.Dispatcher` and you should probably use it if you're
writing your own driver which supports sending interrupts to subscribers.
It is used by `Driver.Circuits.GPIO.Dispatcher` and you should probably use it
if you're writing your own driver which supports sending interrupts to
subscribers.
"""
@type key :: any

View file

@ -88,7 +88,7 @@ defmodule Wafer.Registers do
when is_function(callback, 1) do
with {:ok, data} <- Chip.read_register(conn, 0x01, 1),
new_data when is_binary(new_data) and byte_size(new_data) == 1 <- callback.(data),
{:ok, conn} <- Chip.write_regsiter(conn, 0x01, new_data),
{:ok, conn} <- Chip.write_register(conn, 0x01, new_data),
do: {:ok, conn}
end

View file

@ -4,10 +4,10 @@ defprotocol Wafer.SPI do
@moduledoc """
A (very simple) protocol for interacting with SPI connected devices.
This API is a minimal version of the `ElixirALE.SPI` and `Circuits.SPI` APIs,
except that it takes a `Conn` which implements `SPI` as an argument. If you
want to use any advanced features, such as bus detection, I advise you to
interact with the underlying driver directly.
This API is a minimal version of the `Circuits.SPI` APIs, except that it takes
a `Conn` which implements `SPI` as an argument. If you want to use any
advanced features, such as bus detection, I advise you to interact with the
underlying driver directly.
## Deriving

View file

@ -62,7 +62,6 @@ defmodule Wafer.MixProject do
{:circuits_i2c, "< 3.0.0", optional: true}
end,
{:circuits_spi, "< 3.0.0", optional: true},
{:elixir_ale, "~> 1.2", optional: true},
# Dev/test
{:credo, "~> 1.6", devtest},

View file

@ -9,7 +9,6 @@
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"elixir_ale": {:hex, :elixir_ale, "1.2.1", "07ac2f17a0191b8bd3b0df6b526c7f699a3a4d690c9def573fcb5824eef24d98", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "bfb099137500a3b8c4a1750cf07f2d704897ef9feac3412064bf9edc7d74193c"},
"elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},

View file

@ -1,46 +0,0 @@
defmodule WaferAcceptanceElixirALE.I2CDeviceTest do
use ExUnit.Case, async: true
# Only run acceptance tests if the fake drivers are not loaded.
if System.get_env("SENSE_HAT_PRESENT") == "true" do
alias Wafer.Driver.ElixirALE.I2C, as: Driver
defmodule LPS25H do
use Wafer.Registers
@moduledoc """
A not very useful driver for the LPS25H pressure senser on the Pi Sense Hat.
"""
defregister(:who_am_i, 0x0F, :ro, 1)
defregister(:ctrl_reg1, 0x20, :rw, 1)
def on?(conn) do
case read_ctrl_reg1(conn) do
{:ok, <<1::integer-size(1), _::integer-size(7)>>} -> true
_ -> false
end
end
def turn_on(conn), do: write_ctrl_reg1(conn, <<1::integer-size(1), 0::integer-size(7)>>)
def turn_off(conn), do: write_ctrl_reg1(conn, <<0>>)
end
describe "generated registers" do
test "reading" do
{:ok, conn} = Driver.acquire(bus_name: "i2c-1", address: 0x5C)
assert {:ok, <<0xBD>>} = LPS25H.read_who_am_i(conn)
end
test "reading and writing" do
{:ok, conn} = Driver.acquire(bus_name: "i2c-1", address: 0x5C)
assert {:ok, conn} = LPS25H.turn_on(conn)
assert LPS25H.on?(conn) == true
assert {:ok, conn} = LPS25H.turn_off(conn)
assert LPS25H.on?(conn) == false
end
end
end
end

View file

@ -1,136 +0,0 @@
defmodule WaferDriverElixirALE.GPIO.DispatcherTest do
use ExUnit.Case, async: true
alias Wafer.Driver.ElixirALE.GPIO.Dispatcher, as: Dispatcher
alias Wafer.Driver.ElixirALE.GPIO.Wrapper
alias Wafer.InterruptRegistry, as: IR
import Mimic
@moduledoc false
setup do
Supervisor.terminate_child(Wafer.Supervisor, IR)
Supervisor.restart_child(Wafer.Supervisor, IR)
{:ok, []}
end
describe "handle_call/3" do
test "enabling rising interrupts" do
conn = conn()
Wrapper
|> expect(:set_int, 1, fn pid, pin_condition ->
assert pid == conn.pid
assert pin_condition == :rising
:ok
end)
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:enable, conn, :rising}, nil, state())
end
test "enabling falling interrupts" do
conn = conn()
Wrapper
|> expect(:set_int, 1, fn pid, pin_condition ->
assert pid == conn.pid
assert pin_condition == :falling
:ok
end)
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:enable, conn, :falling}, nil, state())
end
test "enabling both interrupts" do
conn = conn()
Wrapper
|> expect(:set_int, 1, fn pid, pin_condition ->
assert pid == conn.pid
assert pin_condition == :both
:ok
end)
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:enable, conn, :both}, nil, state())
end
test "disabling rising interrupts" do
conn = conn()
Wrapper
|> stub(:set_int, fn _, _ -> :ok end)
Dispatcher.handle_call({:enable, conn, :rising}, nil, state())
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:disable, conn, :rising}, nil, state())
end
test "disabling falling interrupts" do
conn = conn()
Wrapper
|> stub(:set_int, fn _, _ -> :ok end)
Dispatcher.handle_call({:enable, conn, :falling}, nil, state())
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:disable, conn, :falling}, nil, state())
end
test "disabling both interrupts" do
conn = conn()
Wrapper
|> stub(:set_int, fn _, _ -> :ok end)
Dispatcher.handle_call({:enable, conn, :both}, nil, state())
assert {:reply, {:ok, _conn}, _state} =
Dispatcher.handle_call({:disable, conn, :both}, nil, state())
end
end
describe "handle_info/2" do
test "publishing rising interrupts" do
Wrapper
|> stub(:set_int, fn _, _ -> :ok end)
{:reply, {:ok, _conn}, state} =
Dispatcher.handle_call({:enable, conn(), :both}, nil, state())
IR
|> expect(:publish, 1, fn _key, condition ->
assert condition == :rising
{:ok, []}
end)
{:noreply, _state} = Dispatcher.handle_info({:gpio_interrupt, 1, :rising}, state)
end
test "publishing falling interrupts" do
Wrapper
|> stub(:set_int, fn _, _ -> :ok end)
{:reply, {:ok, _conn}, state} =
Dispatcher.handle_call(
{:enable, conn(), :both},
nil,
state()
)
IR
|> expect(:publish, 1, fn _key, condition ->
assert condition == :falling
{:ok, []}
end)
{:noreply, _state} = Dispatcher.handle_info({:gpio_interrupt, 1, :falling}, state)
end
end
defp conn(opts \\ []), do: Enum.into(opts, %{pin: pin(), pid: self()})
defp state(opts \\ []), do: Enum.into(opts, %{})
defp pin, do: 1
end

View file

@ -1,116 +0,0 @@
defmodule WaferDriverElixirALE.GPIOTest do
use ExUnit.Case, async: true
use Mimic
alias Wafer.Driver.ElixirALE.GPIO, as: Subject
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
test "opens the pin and creates the conn" do
Wrapper
|> expect(:start_link, 1, fn pin, direction, opts ->
assert pin == 1
assert direction == :output
assert opts == []
{:ok, self()}
end)
assert {:ok, %Subject{}} = Subject.acquire(pin: 1, direction: :out)
end
test "returns an error when the pin is not specified" do
assert {:error, _} = Subject.acquire([])
end
end
describe "Release.release/1" do
test "closes the pin" do
conn = conn()
Wrapper
|> expect(:release, 1, fn pid ->
assert pid == conn.pid
:ok
end)
assert :ok = Release.release(conn)
end
end
describe "GPIO.read/1" do
test "can read the pin value" do
conn = conn()
Wrapper
|> expect(:read, 1, fn pid ->
assert pid == conn.pid
0
end)
assert {:ok, 0} = GPIO.read(conn)
end
end
describe "GPIO.write/2" do
test "can set the pin value" do
conn = conn()
Wrapper
|> expect(:write, 1, fn pid, value ->
assert pid == conn.pid
assert value == 1
:ok
end)
assert {:ok, %Subject{}} = GPIO.write(conn, 1)
end
end
describe "GPIO.direction/2" do
test "is not supported" do
assert {:error, :not_supported} = GPIO.direction(conn(), :in)
end
end
describe "GPIO.enable_interrupt/2" do
test "subscribes the conn to interrupts" do
conn = conn()
Dispatcher
|> expect(:enable, 1, fn conn1, pin_condition, _metadata ->
assert conn1 == conn
assert pin_condition == :rising
:ok
end)
assert :ok = GPIO.enable_interrupt(conn, :rising)
end
end
describe "GPIO.disable_interrupt/2" do
test "unsubscribes the conn from interrupts" do
conn = conn()
Dispatcher
|> expect(:disable, 1, fn conn1, pin_condition ->
assert conn1 == conn
assert pin_condition == :rising
:ok
end)
assert :ok = GPIO.disable_interrupt(conn, :rising)
end
end
describe "GPIO.pull_mode/2" do
test "is not supported" do
assert {:error, :not_supported} = GPIO.pull_mode(conn(), :pull_up)
end
end
defp conn(opts \\ []), do: struct(%Subject{pin: pin(), pid: self()}, opts)
defp pin, do: 1
end

View file

@ -1,207 +0,0 @@
defmodule WaferElixirALE.I2CTest do
use ExUnit.Case, async: true
use Mimic
alias Wafer.Chip
alias Wafer.Driver.ElixirALE.I2C, as: Subject
alias Wafer.Driver.ElixirALE.I2C.Wrapper
alias Wafer.{I2C, Release}
@moduledoc false
describe "acquire/1" do
test "opens the bus and verifies that the device is present" do
buspid = self()
busname = "i2c-1"
address = 0x13
Wrapper
|> expect(:start_link, 1, fn bus, address ->
assert bus == busname
assert address == 0x13
{:ok, buspid}
end)
|> expect(:detect_devices, 1, fn pid ->
assert buspid == pid
[address]
end)
assert {:ok, %Subject{}} = Subject.acquire(bus_name: busname, address: address)
end
test "when the device is not present on the bus" do
buspid = self()
busname = "i2c-1"
address = 0x13
Wrapper
|> expect(:start_link, 1, fn bus, address ->
assert bus == busname
assert address == 0x13
{:ok, buspid}
end)
|> expect(:detect_devices, 1, fn pid ->
assert buspid == pid
[]
end)
assert {:error, _reason} = Subject.acquire(bus_name: busname, address: address)
end
test "when the device is not present on the bus but an override is forced" do
buspid = self()
busname = "i2c-1"
address = 0x13
Wrapper
|> expect(:start_link, 1, fn bus, address ->
assert bus == busname
assert address == 0x13
{:ok, buspid}
end)
|> expect(:detect_devices, 1, fn pid ->
assert buspid == pid
[]
end)
assert {:ok, %Subject{}} = Subject.acquire(bus_name: busname, address: address, force: true)
end
test "when the bus name is not specified it returns an error" do
assert {:error, _} = Subject.acquire(address: 0x13)
end
test "when the address is not specified it returns an error" do
assert {:error, _} = Subject.acquire(bus_name: "i2c-1")
end
end
describe "Release.release/1" do
test "closes the bus connection" do
conn = conn()
Wrapper
|> expect(:release, 1, fn pid ->
assert pid == conn.pid
:ok
end)
assert :ok = Release.release(conn)
end
end
describe "Chip.read_register/3" do
test "reads from the device's register" do
conn = conn()
Wrapper
|> expect(:write_read, 1, fn pid, data, bytes ->
assert pid == conn.pid
assert data == <<0>>
assert bytes == 2
<<0, 0>>
end)
assert {:ok, <<0, 0>>} = Chip.read_register(conn, 0, 2)
end
end
describe "Chip.write_register/3" do
test "writes to the device's register" do
conn = conn()
Wrapper
|> expect(:write, 1, fn pid, data ->
assert pid == conn.pid
assert data == <<1, 2, 3>>
:ok
end)
assert {:ok, %Subject{}} = Chip.write_register(conn, 1, <<2, 3>>)
end
end
describe "Chip.swap_register/3" do
test "swaps the device's register value for a new value, returning the old value" do
conn = conn()
Wrapper
|> expect(:write_read, 1, fn pid, data, bytes ->
assert pid == conn.pid
assert data == <<0>>
assert bytes == 2
<<0, 0>>
end)
Wrapper
|> expect(:write, 1, fn pid, data ->
assert pid == conn.pid
assert data == <<0, 1, 1>>
:ok
end)
assert {:ok, <<0, 0>>, %Subject{}} = Chip.swap_register(conn, 0, <<1, 1>>)
end
end
describe "I2C.read/2" do
test "reads from the device" do
conn = conn()
Wrapper
|> expect(:read, 1, fn pid, bytes ->
assert pid == conn.pid
assert bytes == 2
<<0, 0>>
end)
assert {:ok, <<0, 0>>} = I2C.read(conn, 2)
end
end
describe "I2C.write/2" do
test "it writes to the device" do
conn = conn()
Wrapper
|> expect(:write, 1, fn pid, data ->
assert pid == conn.pid
assert data == <<0, 0>>
:ok
end)
assert {:ok, %Subject{}} = I2C.write(conn, <<0, 0>>)
end
end
describe "I2C.write_read/3" do
test "it writes to then reads from the device" do
conn = conn()
Wrapper
|> expect(:write_read, 1, fn pid, data, bytes ->
assert pid == conn.pid
assert data == <<1>>
assert bytes == 2
<<0, 0>>
end)
assert {:ok, <<0, 0>>, %Subject{}} = I2C.write_read(conn, <<1>>, 2)
end
end
describe "I2C.detect_devices/1" do
test "it detects devices" do
conn = conn()
Wrapper
|> expect(:detect_devices, 1, fn pid ->
assert conn.pid == pid
[conn.address]
end)
assert {:ok, [0x13]} = I2C.detect_devices(conn)
end
end
defp conn, do: %Subject{pid: self(), bus: "i2c-1", address: 0x13}
end

View file

@ -1,57 +0,0 @@
defmodule WaferElixirALE.SPITest do
use ExUnit.Case, async: true
use Mimic
alias Wafer.Driver.ElixirALE.SPI, as: Subject
alias Wafer.Driver.ElixirALE.SPI.Wrapper
alias Wafer.{Release, SPI}
@moduledoc false
describe "acquire/1" do
test "opens the bus" do
Wrapper
|> expect(:start_link, 1, fn bus, spi_opts, opts ->
assert bus == "spidev0.0"
assert spi_opts == []
assert opts == []
{:ok, self()}
end)
assert {:ok, %Subject{}} = Subject.acquire(bus_name: "spidev0.0")
end
test "when the bus name is not specified it returns an error" do
assert {:error, _} = Subject.acquire([])
end
end
describe "Release.release/1" do
test "closes the bus connection" do
conn = conn()
Wrapper
|> expect(:release, 1, fn pid ->
assert pid == conn.pid
:ok
end)
assert :ok = Release.release(conn)
end
end
describe "SPI.transfer/2" do
test "transfers data to and from the bus" do
conn = conn()
Wrapper
|> expect(:transfer, 1, fn pid, data ->
assert pid == conn.pid
assert data == <<0, 0>>
<<1, 1>>
end)
assert {:ok, <<1, 1>>, %Subject{}} = SPI.transfer(conn, <<0, 0>>)
end
end
defp conn, do: %Subject{pid: self(), bus: "spidev0.0"}
end

View file

@ -3,10 +3,6 @@ Mimic.copy(Wafer.Driver.Circuits.GPIO.Wrapper)
Mimic.copy(Wafer.Driver.Circuits.GPIO.Dispatcher)
Mimic.copy(Wafer.Driver.Circuits.I2C.Wrapper)
Mimic.copy(Wafer.Driver.Circuits.SPI.Wrapper)
Mimic.copy(Wafer.Driver.ElixirALE.GPIO.Wrapper)
Mimic.copy(Wafer.Driver.ElixirALE.GPIO.Dispatcher)
Mimic.copy(Wafer.Driver.ElixirALE.I2C.Wrapper)
Mimic.copy(Wafer.Driver.ElixirALE.SPI.Wrapper)
Mimic.copy(Wafer.InterruptRegistry)
ExUnit.start()