feat!: Remove ElixirALE support. #67
25 changed files with 23 additions and 1026 deletions
10
README.md
10
README.md
|
@ -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 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/).
|
Documentation for the main branch can always be found [here](https://docs.harton.nz/james/wafer/).
|
||||||
|
|
||||||
|
@ -112,10 +112,10 @@ end
|
||||||
|
|
||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
I've included stub implementations of the parts of `ElixirALE` and `Circuits`
|
I've included stub implementations of the parts of `Circuits` that are
|
||||||
that are interacted with by this project, so the tests should run and pass on
|
interacted with by this project, so the tests should run and pass on machines
|
||||||
machines without physical hardware interfaces. If you have a Raspberry Pi with
|
without physical hardware interfaces. If you have a Raspberry Pi with a Pi Sense
|
||||||
a Pi Sense Hat connected you can run the tests with the `SENSE_HAT_PRESENT=true`
|
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
|
environment variable set and it will perform integration tests with two of the
|
||||||
sensors on this device.
|
sensors on this device.
|
||||||
|
|
||||||
|
|
|
@ -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
|
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
|
repetitive tasks of writing drivers for hardware peripherals such as I2C and
|
||||||
SPI connected sensors.
|
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
|
end
|
||||||
|
|
|
@ -9,7 +9,6 @@ defmodule Wafer.Application do
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
{Registry, [keys: :duplicate, name: Wafer.InterruptRegistry]},
|
{Registry, [keys: :duplicate, name: Wafer.InterruptRegistry]},
|
||||||
Wafer.Driver.ElixirALE.GPIO.Dispatcher,
|
|
||||||
Wafer.Driver.Circuits.GPIO.Dispatcher
|
Wafer.Driver.Circuits.GPIO.Dispatcher
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ defprotocol Wafer.Chip do
|
||||||
|
|
||||||
## Example
|
## 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)
|
...> Chip.read_register(conn, 0, 1)
|
||||||
{:ok, <<0>>}
|
{:ok, <<0>>}
|
||||||
"""
|
"""
|
||||||
|
@ -71,7 +71,7 @@ defprotocol Wafer.Chip do
|
||||||
|
|
||||||
## Example
|
## 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>>)
|
...> Chip.write_register(conn, 0, <<0>>)
|
||||||
{:ok, conn}
|
{:ok, conn}
|
||||||
"""
|
"""
|
||||||
|
@ -88,7 +88,7 @@ defprotocol Wafer.Chip do
|
||||||
|
|
||||||
- `conn` a type which implements the `Wafer.Conn` behaviour.
|
- `conn` a type which implements the `Wafer.Conn` behaviour.
|
||||||
- `register_address` the address of the register to swap.
|
- `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
|
## Returns
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ defprotocol Wafer.Chip do
|
||||||
|
|
||||||
## Example
|
## 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>>)
|
...> Chip.swap_register(conn, 0, <<1>>)
|
||||||
{:ok, <<0>>, conn}
|
{:ok, <<0>>, conn}
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,13 +5,13 @@ defmodule Wafer.Driver.Circuits.GPIO.Wrapper do
|
||||||
defdelegate close(gpio), to: Circuits.GPIO
|
defdelegate close(gpio), to: Circuits.GPIO
|
||||||
|
|
||||||
@compile {:no_warn_undefined, 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}
|
@compile {:no_warn_undefined, Circuits.GPIO}
|
||||||
defdelegate open(pin_number, pin_direction, options \\ []), to: Circuits.GPIO
|
defdelegate open(pin_number, pin_direction, options \\ []), to: Circuits.GPIO
|
||||||
|
|
||||||
@compile {:no_warn_undefined, 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}
|
@compile {:no_warn_undefined, Circuits.GPIO}
|
||||||
defdelegate read(gpio), to: Circuits.GPIO
|
defdelegate read(gpio), to: Circuits.GPIO
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -68,13 +68,12 @@ defprotocol Wafer.GPIO do
|
||||||
Interrupts will be sent to the calling process as messages in the form of
|
Interrupts will be sent to the calling process as messages in the form of
|
||||||
`{:interrupt, Conn.t(), pin_condition, metadata | nil}`.
|
`{:interrupt, Conn.t(), pin_condition, metadata | nil}`.
|
||||||
|
|
||||||
## Implementors note
|
## Implementers note
|
||||||
|
|
||||||
`Wafer` starts it's own `Registry` named `Wafer.InterruptRegistry` which you
|
`Wafer` starts it's own `Registry` named `Wafer.InterruptRegistry` which you
|
||||||
can use to publish your interrupts to using the above format. The registry
|
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
|
key is set as follows: `{PublishingModule, pin, pin_condition}`. You can see
|
||||||
examples in the `Circuits.GPIO.Dispatcher` and `ElixirALE.GPIO.Dispatcher`
|
examples in the `Circuits.GPIO.Dispatcher` module.
|
||||||
modules.
|
|
||||||
"""
|
"""
|
||||||
@spec enable_interrupt(Conn.t(), pin_condition, any) ::
|
@spec enable_interrupt(Conn.t(), pin_condition, any) ::
|
||||||
{:ok, Conn.t()} | {:error, reason :: any}
|
{:ok, Conn.t()} | {:error, reason :: any}
|
||||||
|
|
|
@ -6,8 +6,8 @@ defprotocol Wafer.I2C do
|
||||||
want to use the `Chip` protocol for working with registers, but this is
|
want to use the `Chip` protocol for working with registers, but this is
|
||||||
provided for consistency's sake.
|
provided for consistency's sake.
|
||||||
|
|
||||||
This API is extremely similar to the `ElixirALE.I2C` and `Circuits.I2C` APIs,
|
This API is extremely similar to the `Circuits.I2C` APIs, except that it takes
|
||||||
except that it takes a `Conn` which implements `I2C` as an argument.
|
a `Conn` which implements `I2C` as an argument.
|
||||||
|
|
||||||
## Deriving
|
## Deriving
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ defmodule Wafer.InterruptRegistry do
|
||||||
This module provides Wafer's interrupt registry. This allows multiple
|
This module provides Wafer's interrupt registry. This allows multiple
|
||||||
subscribers to be subscribed to interrupts from many different pins.
|
subscribers to be subscribed to interrupts from many different pins.
|
||||||
|
|
||||||
It is used by `Driver.Circuits.GPIO.Dispatcher` and
|
It is used by `Driver.Circuits.GPIO.Dispatcher` and you should probably use it
|
||||||
`Driver.ElixirALE.GPIO.Dispatcher` and you should probably use it if you're
|
if you're writing your own driver which supports sending interrupts to
|
||||||
writing your own driver which supports sending interrupts to subscribers.
|
subscribers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@type key :: any
|
@type key :: any
|
||||||
|
|
|
@ -88,7 +88,7 @@ defmodule Wafer.Registers do
|
||||||
when is_function(callback, 1) do
|
when is_function(callback, 1) do
|
||||||
with {:ok, data} <- Chip.read_register(conn, 0x01, 1),
|
with {:ok, data} <- Chip.read_register(conn, 0x01, 1),
|
||||||
new_data when is_binary(new_data) and byte_size(new_data) == 1 <- callback.(data),
|
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}
|
do: {:ok, conn}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ defprotocol Wafer.SPI do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
A (very simple) protocol for interacting with SPI connected devices.
|
A (very simple) protocol for interacting with SPI connected devices.
|
||||||
|
|
||||||
This API is a minimal version of the `ElixirALE.SPI` and `Circuits.SPI` APIs,
|
This API is a minimal version of the `Circuits.SPI` APIs, except that it takes
|
||||||
except that it takes a `Conn` which implements `SPI` as an argument. If you
|
a `Conn` which implements `SPI` as an argument. If you want to use any
|
||||||
want to use any advanced features, such as bus detection, I advise you to
|
advanced features, such as bus detection, I advise you to interact with the
|
||||||
interact with the underlying driver directly.
|
underlying driver directly.
|
||||||
|
|
||||||
## Deriving
|
## Deriving
|
||||||
|
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -62,7 +62,6 @@ defmodule Wafer.MixProject do
|
||||||
{:circuits_i2c, "< 3.0.0", optional: true}
|
{:circuits_i2c, "< 3.0.0", optional: true}
|
||||||
end,
|
end,
|
||||||
{:circuits_spi, "< 3.0.0", optional: true},
|
{:circuits_spi, "< 3.0.0", optional: true},
|
||||||
{:elixir_ale, "~> 1.2", optional: true},
|
|
||||||
|
|
||||||
# Dev/test
|
# Dev/test
|
||||||
{:credo, "~> 1.6", devtest},
|
{:credo, "~> 1.6", devtest},
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -9,7 +9,6 @@
|
||||||
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
|
"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": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
"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"},
|
"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"},
|
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||||
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
|
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -3,10 +3,6 @@ Mimic.copy(Wafer.Driver.Circuits.GPIO.Wrapper)
|
||||||
Mimic.copy(Wafer.Driver.Circuits.GPIO.Dispatcher)
|
Mimic.copy(Wafer.Driver.Circuits.GPIO.Dispatcher)
|
||||||
Mimic.copy(Wafer.Driver.Circuits.I2C.Wrapper)
|
Mimic.copy(Wafer.Driver.Circuits.I2C.Wrapper)
|
||||||
Mimic.copy(Wafer.Driver.Circuits.SPI.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)
|
Mimic.copy(Wafer.InterruptRegistry)
|
||||||
|
|
||||||
ExUnit.start()
|
ExUnit.start()
|
||||||
|
|
Loading…
Reference in a new issue