Make all our protocol derivable.

This commit is contained in:
James Harton 2019-12-31 18:54:46 +13:00
parent ba68653925
commit 13f79bf72e
18 changed files with 431 additions and 16 deletions

View file

@ -43,6 +43,15 @@ defmodule HTS221 do
end 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 `FAKE_DRIVERS=false`
environment variable set and it will perform integration tests with two of the
sensors on this device.
## Installation ## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed If [available in Hex](https://hex.pm/docs/publish), the package can be installed

View file

@ -4,6 +4,32 @@ defprotocol Wafer.Chip do
@moduledoc """ @moduledoc """
A `Chip` is a physical peripheral with registers which can be read from and A `Chip` is a physical peripheral with registers which can be read from and
written to. written to.
Rather than interacting with this protocol directly, it's a lot easier to use
the macros in `Wafer.Registers` to do it for you.
## Deriving
If you're implementing your own `Conn` type which simply delegates to one of
the lower level drivers then you can derive this protocol automatically:
```elixir
defmodule MyConnection do
@derive Wafer.Chip
defstruct [:conn]
end
```
If your type uses a key other than `conn` for the inner connection you can
specify it while deriving:
```elixir
defmodule MyConnection do
@derive {Wafer.Chip, key: :i2c_conn}
defstruct [:i2c_conn]
end
```
""" """
@type register_address :: non_neg_integer @type register_address :: non_neg_integer
@ -41,7 +67,7 @@ defprotocol Wafer.Chip do
iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68) iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68)
...> Chip.write_register(conn, 0, <<0>>) ...> Chip.write_register(conn, 0, <<0>>)
:ok {:ok, conn}
""" """
@spec write_register(Conn.t(), register_address, data :: binary) :: @spec write_register(Conn.t(), register_address, data :: binary) ::
{:ok, t} | {:error, reason :: any} {:ok, t} | {:error, reason :: any}
@ -66,9 +92,53 @@ defprotocol Wafer.Chip do
iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68) iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68)
...> Chip.swap_register(conn, 0, <<1>>) ...> Chip.swap_register(conn, 0, <<1>>)
{:ok, <<0>>} {:ok, <<0>>, conn}
""" """
@spec swap_register(Conn.t(), register_address, new_data :: binary) :: @spec swap_register(Conn.t(), register_address, new_data :: binary) ::
{:ok, data :: binary, t} | {:error, reason :: any} {:ok, data :: binary, t} | {:error, reason :: any}
def swap_register(conn, register_address, new_data) def swap_register(conn, register_address, new_data)
end end
defimpl Wafer.Chip, 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.Chip` for `#{module}`: key `#{inspect(key)}` not present in struct."
)
end
quote do
defimpl Wafer.Chip, for: unquote(module) do
import Wafer.Guards
alias Wafer.Chip
def read_register(%{unquote(key) => inner_conn}, register_address, bytes)
when is_register_address(register_address) and is_byte_size(bytes),
do: Chip.read_register(inner_conn, register_address, bytes)
def write_register(%{unquote(key) => inner_conn} = conn, register_address, data)
when is_register_address(register_address) and is_binary(data) do
with {:ok, inner_conn} <- Chip.write_register(inner_conn, register_address, data),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def swap_register(%{unquote(key) => inner_conn} = conn, register_address, new_data) do
with {:ok, data, inner_conn} <-
Chip.swap_register(inner_conn, register_address, new_data),
do: {:ok, data, Map.put(conn, unquote(key), inner_conn)}
end
end
end
end
def read_register(unknown, _register_address, _bytes),
do: {:error, "`Wafer.Chip` not implemented for `#{inspect(unknown)}`"}
def write_register(unknown, _register_address, _data),
do: {:error, "`Wafer.Chip` not implemented for `#{inspect(unknown)}`"}
def swap_register(unknown, _register_address, _new_data),
do: {:error, "`Wafer.Chip` not implemented for `#{inspect(unknown)}`"}
end

View file

@ -1,6 +1,32 @@
defmodule Wafer.Conn do defmodule Wafer.Conn do
@moduledoc """ @moduledoc """
Defines a protocol and behaviour for connecting to a peripheral. Defines a behaviour for connecting to a peripheral.
This behaviour is used by all the driver types in `Wafer` and you should
implement it for your devices also.
## Example
Implementing `Conn` for a `HTS221` chip connected via Circuits' I2C driver.
```elixir
defmodule HTS221 do
defstruct ~w[conn]a
alias Wafer.Drivers.CircuitsI2C, as: Driver
@behaviour Wafer.Conn
@default_bus "i2c-1"
@default_address 0x5F
def acquire(opts) when is_list(opts) do
bus = Keyword.get(opts, :bus, @default_bus)
address = Keyword.get(opts, :address, @default_address)
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
```
""" """
@type options :: [option] @type options :: [option]

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.CircuitsGPIO do
@moduledoc """ @moduledoc """
A connection to a native GPIO pin via Circuits' GPIO driver. A connection to a native GPIO pin via Circuits' GPIO driver.
Implements the `Wafer.Conn` behaviour as well as the `Wafer.GPIO` protocol.
""" """
@type t :: %__MODULE__{ref: reference, pin: non_neg_integer, direction: GPIO.pin_direction()} @type t :: %__MODULE__{ref: reference, pin: non_neg_integer, direction: GPIO.pin_direction()}
@ -41,7 +43,7 @@ defmodule Wafer.Driver.CircuitsGPIO do
Note that other connections may still be using the pin. Note that other connections may still be using the pin.
""" """
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{ref: ref}) when is_reference(ref), do: Driver.close(ref) def release(%__MODULE__{ref: ref} = _conn) when is_reference(ref), do: Driver.close(ref)
end end
defimpl Wafer.GPIO, for: Wafer.Driver.CircuitsGPIO do defimpl Wafer.GPIO, for: Wafer.Driver.CircuitsGPIO do
@ -68,7 +70,9 @@ defimpl Wafer.GPIO, for: Wafer.Driver.CircuitsGPIO do
def direction(%{ref: ref} = conn, direction) def direction(%{ref: ref} = conn, direction)
when is_reference(ref) and is_pin_direction(direction) do when is_reference(ref) and is_pin_direction(direction) do
case(Driver.set_direction(ref, direction)) do pin_dir = String.to_atom(Enum.join([direction, "put"], ""))
case(Driver.set_direction(ref, pin_dir)) do
:ok -> {:ok, %{conn | direction: direction}} :ok -> {:ok, %{conn | direction: direction}}
{:error, reason} -> {:error, reason} {:error, reason} -> {:error, reason}
end end

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.CircuitsI2C do
@moduledoc """ @moduledoc """
A connection to a chip via Circuits' I2C driver. A connection to a chip via Circuits' 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, ref: reference} @type t :: %__MODULE__{address: I2C.address(), bus: binary, ref: reference}
@ -38,7 +40,7 @@ defmodule Wafer.Driver.CircuitsI2C do
end end
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{ref: ref}), do: Driver.close(ref) def release(%__MODULE__{ref: ref} = _conn), do: Driver.close(ref)
end end
defimpl Wafer.Chip, for: Wafer.Driver.CircuitsI2C do defimpl Wafer.Chip, for: Wafer.Driver.CircuitsI2C do

View file

@ -5,6 +5,8 @@ defmodule Wafer.Driver.CircuitsSPI do
@moduledoc """ @moduledoc """
A connection to a chip via Circuits's SPI driver. A connection to a chip via Circuits's SPI driver.
Implements the `Wafer.Conn` behaviour as well as the `Wafer.SPI` protocol.
""" """
@type t :: %__MODULE__{bus: binary, ref: reference} @type t :: %__MODULE__{bus: binary, ref: reference}
@ -37,7 +39,7 @@ defmodule Wafer.Driver.CircuitsSPI do
Close the SPI bus connection. Close the SPI bus connection.
""" """
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{ref: ref}) when is_reference(ref), do: Driver.close(ref) def release(%__MODULE__{ref: ref} = _conn) when is_reference(ref), do: Driver.close(ref)
end end
defimpl Wafer.SPI, for: Wafer.Driver.CircuitsSPI do defimpl Wafer.SPI, for: Wafer.Driver.CircuitsSPI do

View file

@ -6,6 +6,8 @@ defmodule Wafer.Driver.ElixirALEGPIO do
@moduledoc """ @moduledoc """
A connection to a native GPIO pin via ElixirALE's GPIO driver. 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 t :: %__MODULE__{pid: pid}
@ -39,7 +41,7 @@ defmodule Wafer.Driver.ElixirALEGPIO do
Note that other connections may still be using the pin. Note that other connections may still be using the pin.
""" """
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{pid: pid}), do: Driver.release(pid) def release(%__MODULE__{pid: pid} = _conn), do: Driver.release(pid)
end end
defimpl Wafer.GPIO, for: Wafer.Driver.ElixirALEGPIO do defimpl Wafer.GPIO, for: Wafer.Driver.ElixirALEGPIO do

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.ElixirALEI2C do
@moduledoc """ @moduledoc """
A connection to a chip via ElixirALE's I2C driver. 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 t :: %__MODULE__{address: I2C.address(), bus: binary, pid: pid}
@ -39,7 +41,7 @@ defmodule Wafer.Driver.ElixirALEI2C do
end end
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{pid: pid}) when is_pid(pid), do: ElixirALE.I2C.release(pid) def release(%__MODULE__{pid: pid} = _conn) when is_pid(pid), do: ElixirALE.I2C.release(pid)
end end
defimpl Wafer.Chip, for: Wafer.Driver.ElixirALEI2C do defimpl Wafer.Chip, for: Wafer.Driver.ElixirALEI2C do

View file

@ -5,6 +5,8 @@ defmodule Wafer.Driver.ElixirALESPI do
@moduledoc """ @moduledoc """
A connection to a chip via ElixirALE's SPI driver. 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 t :: %__MODULE__{bus: binary, pid: pid}
@ -38,7 +40,7 @@ defmodule Wafer.Driver.ElixirALESPI do
Close the SPI bus connection. Close the SPI bus connection.
""" """
@spec release(t) :: :ok | {:error, reason :: any} @spec release(t) :: :ok | {:error, reason :: any}
def release(%__MODULE__{pid: pid}) when is_pid(pid), do: Driver.release(pid) def release(%__MODULE__{pid: pid} = _conn) when is_pid(pid), do: Driver.release(pid)
end end
defimpl Wafer.SPI, for: Wafer.Driver.ElixirALESPI do defimpl Wafer.SPI, for: Wafer.Driver.ElixirALESPI do

View file

@ -3,6 +3,31 @@ defprotocol Wafer.GPIO do
@moduledoc """ @moduledoc """
A `GPIO` is a physical pin which can be read from and written to. A `GPIO` is a physical pin which can be read from and written to.
Some hardware supports interrupts, some has internal pull up/down resistors.
Wafer supports all of these, however not all drivers do.
## Deriving
If you're implementing your own `Conn` type that simply delegates to one of
the lower level drivers then you can derive this protocol automatically:
```elixir
defstruct MyPin do
@derive Wafer.GPIO
defstruct [:conn]
end
```
If your type uses a key other than `conn` for the inner connection you can
specify it while deriving:
```elixir
defstruct MyPin do
@derive {Wafer.GPIO, key: :gpio_conn}
defstruct [:gpio_conn]
end
```
""" """
@type pin_direction :: :in | :out @type pin_direction :: :in | :out
@ -64,3 +89,73 @@ defprotocol Wafer.GPIO do
@spec pull_mode(Conn.t(), pull_mode) :: {:ok, Conn.t()} | {:error, reason :: any} @spec pull_mode(Conn.t(), pull_mode) :: {:ok, Conn.t()} | {:error, reason :: any}
def pull_mode(conn, pull_mode) def pull_mode(conn, pull_mode)
end end
defimpl Wafer.GPIO, 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.GPIO` for `#{module}`: key `#{inspect(key)}` not present in struct."
)
end
quote do
defimpl Wafer.GPIO, for: unquote(module) do
import Wafer.Guards
alias Wafer.GPIO
def read(%{unquote(key) => inner_conn} = conn) do
with {:ok, pin_value, inner_conn} <- GPIO.read(inner_conn),
do: {:ok, pin_value, Map.put(conn, unquote(key), inner_conn)}
end
def write(%{unquote(key) => inner_conn} = conn, pin_value) when is_pin_value(pin_value) do
with {:ok, inner_conn} <- GPIO.write(inner_conn, pin_value),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def direction(%{unquote(key) => inner_conn} = conn, pin_direction)
when is_pin_direction(pin_direction) do
with {:ok, inner_conn} <- GPIO.direction(inner_conn, pin_direction),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def enable_interrupt(%{unquote(key) => inner_conn} = conn, pin_condition)
when is_pin_condition(pin_condition) do
with {:ok, inner_conn} <- GPIO.enable_interrupt(inner_conn, pin_condition),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def disable_interrupt(%{unquote(key) => inner_conn} = conn, pin_condition)
when is_pin_condition(pin_condition) do
with {:ok, inner_conn} <- GPIO.disable_interrupt(inner_conn, pin_condition),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def pull_mode(%{unquote(key) => inner_conn} = conn, pull_mode)
when is_pin_pull_mode(pull_mode) do
with {:ok, inner_conn} <- GPIO.pull_mode(inner_conn, pull_mode),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
end
end
end
def read(unknown), do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def write(unknown, _pin_value),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def direction(unknown, _pin_direction),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def enable_interrupt(unknown, _pin_condition),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
def disable_interrupt(unknown, _pin_condition),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
def pull_mode(unknown, _pull_mode),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
end

View file

@ -3,7 +3,7 @@ defmodule Wafer.Guards do
Handy guards which you can use in your code to assert correct values. Handy guards which you can use in your code to assert correct values.
""" """
@doc "A positive integer" @doc "Pin numbers are non negative integers"
defguard is_pin_number(pin) when is_integer(pin) and pin >= 0 defguard is_pin_number(pin) when is_integer(pin) and pin >= 0
@doc "Either `:in` or `:out`" @doc "Either `:in` or `:out`"
@ -21,9 +21,9 @@ defmodule Wafer.Guards do
@doc "An integer between `0` and `0x7F`" @doc "An integer between `0` and `0x7F`"
defguard is_i2c_address(address) when is_integer(address) and address >= 0 and address <= 0x7F defguard is_i2c_address(address) when is_integer(address) and address >= 0 and address <= 0x7F
@doc "A positive integer" @doc "Register addresses are non negative integers usually only one byte, but we don't enforce that here"
defguard is_register_address(address) when is_integer(address) and address >= 0 defguard is_register_address(address) when is_integer(address) and address >= 0
@doc "A positive integer" @doc "Byte sizes are non negative integers"
defguard is_byte_size(bytes) when is_integer(bytes) and bytes >= 0 defguard is_byte_size(bytes) when is_integer(bytes) and bytes >= 0
end end

View file

@ -5,6 +5,30 @@ defprotocol Wafer.I2C do
A protocol for interacting with I2C devices directly. Most of the time you'll A protocol for interacting with I2C devices directly. Most of the time you'll
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,
except that it takes a `Conn` which implements `I2C` as an argument.
## Deriving
If you're implementing your own `Conn` type that simply delegates to one of
the lower level drivers then you can derive this protocol automatically:
```elixir
defstruct MyI2CDevice do
@derive Wafer.I2C
defstruct [:conn]
end
```
If your type uses a key other than `conn` for the inner connection you can
specify it while deriving:
```elixir
defstruct MyI2CDevice do
@derive {Wafer.I2C, key: :i2c_conn}
defstruct [:i2c_conn]
end
""" """
@type address :: 0..0x7F @type address :: 0..0x7F
@ -42,3 +66,54 @@ defprotocol Wafer.I2C do
@spec detect_devices(Conn.t()) :: {:ok, [address]} @spec detect_devices(Conn.t()) :: {:ok, [address]}
def detect_devices(conn) def detect_devices(conn)
end end
defimpl Wafer.I2C, 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.I2C` for `#{module}`: key `#{inspect(key)}` not present in struct."
)
end
quote do
defimpl Wafer.I2C, for: unquote(module) do
import Wafer.Guards
alias Wafer.I2C
def read(%{unquote(key) => inner_conn}, bytes_to_read, options \\ [])
when is_byte_size(bytes_to_read) and is_list(options) do
I2C.read(inner_conn, bytes_to_read, options)
end
def write(%{unquote(key) => inner_conn} = conn, data, options \\ [])
when is_binary(data) and is_list(options) do
with {:ok, inner_conn} <- I2C.write(inner_conn, data, options),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def write_read(%{unquote(key) => inner_conn} = conn, data, bytes_to_read, options \\ [])
when is_binary(data) and is_byte_size(bytes_to_read) and is_list(options) do
with {:ok, data, inner_conn} <-
I2C.write_read(inner_conn, data, bytes_to_read, options),
do: {:ok, data, Map.put(conn, unquote(key), inner_conn)}
end
def detect_devices(%{unquote(key) => inner_conn}), do: I2C.detect_devices(inner_conn)
end
end
end
def read(unknown, _bytes_to_read, _options \\ []),
do: {:error, "`Wafer.I2C` not implemented for `#{inspect(unknown)}`"}
def write(unknown, _data, _options \\ []),
do: {:error, "`Wafer.I2C` not implemented for `#{inspect(unknown)}`"}
def write_read(unknown, _data, _bytes_to_read, _options \\ []),
do: {:error, "`Wafer.I2C` not implemented for `#{inspect(unknown)}`"}
def detect_devices(unknown),
do: {:error, "`Wafer.I2C` not implemented for `#{inspect(unknown)}`"}
end

View file

@ -3,6 +3,9 @@ defmodule Wafer.Registers do
@moduledoc """ @moduledoc """
This module provides helpful macros for specifying the registers used to This module provides helpful macros for specifying the registers used to
communicate with your device. communicate with your device.
This can be a massive time saver, and means you can basically just copy them
straight out of the datasheet.
""" """
alias Wafer.Chip alias Wafer.Chip
alias Wafer.Conn alias Wafer.Conn

View file

@ -3,6 +3,32 @@ 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,
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
If you're implementing your own `Conn` type that simply delegates to one of
the lower level drivers then you can derive this protocol automatically:
```elixir
defstruct MySPIDevice do
@derive Wafer.SPI
defstruct [:conn]
end
```
If your type uses a key other than `conn` for the inner connection you can
specify it while deriving:
```elixir
defstruct MySPIDevice do
@derive {Wafer.SPI, key: :spi_conn}
defstruct [:spi_conn]
end
""" """
@type data :: binary @type data :: binary
@ -17,3 +43,31 @@ defprotocol Wafer.SPI do
@spec transfer(Conn.t(), data) :: {:ok, data, Conn.t()} | {:error, reason :: any} @spec transfer(Conn.t(), data) :: {:ok, data, Conn.t()} | {:error, reason :: any}
def transfer(conn, data) def transfer(conn, data)
end end
defimpl Wafer.SPI, 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.SPI` for `#{module}`: key `#{inspect(key)}` not present in struct."
)
end
quote do
defimpl Wafer.SPI, for: unquote(module) do
import Wafer.Guards
alias Wafer.SPI
def transfer(%{unquote(key) => inner_conn}, data)
when is_binary(data) do
with {:ok, data, inner_conn} <- SPI.transfer(inner_conn, data),
do: {:ok, data, Map.put(conn, unquote(key), inner_conn)}
end
end
end
end
def transfer(unknown, _data),
do: {:error, "`Wafer.SPI` not implemented for `#{inspect(unknown)}`"}
end

25
mix.exs
View file

@ -2,14 +2,23 @@ defmodule Wafer.MixProject do
use Mix.Project use Mix.Project
@moduledoc false @moduledoc false
@description """
Wafer is an Elixir library to make writing drivers for i2c and SPI connected
peripherals and interacting with GPIO pins easier.
"""
@version "0.1.0"
def project do def project do
[ [
app: :wafer, app: :wafer,
version: "0.1.0", version: @version,
elixir: "~> 1.9", elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
deps: deps() package: package(),
description: @description,
deps: deps(),
consolidate_protocols: Mix.env() != :test
] ]
end end
@ -21,9 +30,21 @@ defmodule Wafer.MixProject do
] ]
end end
def package do
[
maintainers: ["James Harton <james@automat.nz>"],
licenses: ["MIT"],
links: %{
"Source" => "https://gitlab.com/jimsy/wafer"
}
]
end
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
{:ex_doc, ">= 0.0.0", only: :dev},
{:earmark, ">= 0.0.0", only: :dev},
{:mimic, "~> 1.1", only: :test}, {:mimic, "~> 1.1", only: :test},
{:credo, "~> 1.1", only: [:dev, :test], runtime: false}, {:credo, "~> 1.1", only: [:dev, :test], runtime: false},
{:elixir_ale, "~> 1.2", optional: true}, {:elixir_ale, "~> 1.2", optional: true},

View file

@ -4,8 +4,13 @@
"circuits_i2c": {:hex, :circuits_i2c, "0.3.5", "43e043d7efc3aead364061f8a7ed627f81ff7cef52bfa47cb629d8a68ca56a9f", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "circuits_i2c": {:hex, :circuits_i2c, "0.3.5", "43e043d7efc3aead364061f8a7ed627f81ff7cef52bfa47cb629d8a68ca56a9f", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"circuits_spi": {:hex, :circuits_spi, "0.1.4", "b64161e0a25837bdb3301fbc5754d52278a916b7a549065fba4dc107395a930e", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "circuits_spi": {:hex, :circuits_spi, "0.1.4", "b64161e0a25837bdb3301fbc5754d52278a916b7a549065fba4dc107395a930e", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
"elixir_ale": {:hex, :elixir_ale, "1.2.1", "07ac2f17a0191b8bd3b0df6b526c7f699a3a4d690c9def573fcb5824eef24d98", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_ale": {:hex, :elixir_ale, "1.2.1", "07ac2f17a0191b8bd3b0df6b526c7f699a3a4d690c9def573fcb5824eef24d98", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"}, "mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
} }

43
test/chip_test.exs Normal file
View file

@ -0,0 +1,43 @@
defmodule WaferChipTest do
use ExUnit.Case, async: true
alias Wafer.Chip
describe "__deriving__/3" do
test "deriving with default key name" do
mod = test_mod()
assert Chip.impl_for!(struct(mod, conn: :noop))
end
test "deriving with a specified key name" do
mod = test_mod(:marty)
assert Chip.impl_for!(struct(mod, fruit: :noop))
end
end
defp test_mod(key \\ :conn) do
mod = random_module_name()
if key == :conn do
defmodule mod do
@derive Chip
defstruct [:conn]
end
else
defmodule mod do
@derive {Chip, key: key}
defstruct [key]
end
end
mod
end
defp random_module_name do
name =
16
|> :crypto.strong_rand_bytes()
|> Base.encode64(padding: false)
Module.concat(__MODULE__, name)
end
end

View file

@ -79,7 +79,7 @@ defmodule WaferDriverCircuitsGPIOTest do
Driver Driver
|> expect(:set_direction, 1, fn ref, direction -> |> expect(:set_direction, 1, fn ref, direction ->
assert ref == conn.ref assert ref == conn.ref
assert direction == :in assert direction == :input
:ok :ok
end) end)