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
```
## 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
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 """
A `Chip` is a physical peripheral with registers which can be read from and
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
@ -41,7 +67,7 @@ defprotocol Wafer.Chip do
iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68)
...> Chip.write_register(conn, 0, <<0>>)
:ok
{:ok, conn}
"""
@spec write_register(Conn.t(), register_address, data :: binary) ::
{:ok, t} | {:error, reason :: any}
@ -66,9 +92,53 @@ defprotocol Wafer.Chip do
iex> {:ok, conn} = ElixirALEI2C.acquire(bus: "i2c", address: 0x68)
...> Chip.swap_register(conn, 0, <<1>>)
{:ok, <<0>>}
{:ok, <<0>>, conn}
"""
@spec swap_register(Conn.t(), register_address, new_data :: binary) ::
{:ok, data :: binary, t} | {:error, reason :: any}
def swap_register(conn, register_address, new_data)
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
@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]

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.CircuitsGPIO do
@moduledoc """
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()}
@ -41,7 +43,7 @@ defmodule Wafer.Driver.CircuitsGPIO do
Note that other connections may still be using the pin.
"""
@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
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)
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}}
{:error, reason} -> {:error, reason}
end

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.CircuitsI2C do
@moduledoc """
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}
@ -38,7 +40,7 @@ defmodule Wafer.Driver.CircuitsI2C do
end
@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
defimpl Wafer.Chip, for: Wafer.Driver.CircuitsI2C do

View file

@ -5,6 +5,8 @@ defmodule Wafer.Driver.CircuitsSPI do
@moduledoc """
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}
@ -37,7 +39,7 @@ defmodule Wafer.Driver.CircuitsSPI do
Close the SPI bus connection.
"""
@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
defimpl Wafer.SPI, for: Wafer.Driver.CircuitsSPI do

View file

@ -6,6 +6,8 @@ defmodule Wafer.Driver.ElixirALEGPIO do
@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}
@ -39,7 +41,7 @@ defmodule Wafer.Driver.ElixirALEGPIO do
Note that other connections may still be using the pin.
"""
@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
defimpl Wafer.GPIO, for: Wafer.Driver.ElixirALEGPIO do

View file

@ -7,6 +7,8 @@ defmodule Wafer.Driver.ElixirALEI2C do
@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}
@ -39,7 +41,7 @@ defmodule Wafer.Driver.ElixirALEI2C do
end
@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
defimpl Wafer.Chip, for: Wafer.Driver.ElixirALEI2C do

View file

@ -5,6 +5,8 @@ defmodule Wafer.Driver.ElixirALESPI do
@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}
@ -38,7 +40,7 @@ defmodule Wafer.Driver.ElixirALESPI do
Close the SPI bus connection.
"""
@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
defimpl Wafer.SPI, for: Wafer.Driver.ElixirALESPI do

View file

@ -3,6 +3,31 @@ defprotocol Wafer.GPIO do
@moduledoc """
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
@ -64,3 +89,73 @@ defprotocol Wafer.GPIO do
@spec pull_mode(Conn.t(), pull_mode) :: {:ok, Conn.t()} | {:error, reason :: any}
def pull_mode(conn, pull_mode)
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.
"""
@doc "A positive integer"
@doc "Pin numbers are non negative integers"
defguard is_pin_number(pin) when is_integer(pin) and pin >= 0
@doc "Either `:in` or `:out`"
@ -21,9 +21,9 @@ defmodule Wafer.Guards do
@doc "An integer between `0` and `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
@doc "A positive integer"
@doc "Byte sizes are non negative integers"
defguard is_byte_size(bytes) when is_integer(bytes) and bytes >= 0
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
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.
## 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
@ -42,3 +66,54 @@ defprotocol Wafer.I2C do
@spec detect_devices(Conn.t()) :: {:ok, [address]}
def detect_devices(conn)
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 """
This module provides helpful macros for specifying the registers used to
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.Conn

View file

@ -3,6 +3,32 @@ 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.
## 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
@ -17,3 +43,31 @@ defprotocol Wafer.SPI do
@spec transfer(Conn.t(), data) :: {:ok, data, Conn.t()} | {:error, reason :: any}
def transfer(conn, data)
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
@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
[
app: :wafer,
version: "0.1.0",
version: @version,
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
package: package(),
description: @description,
deps: deps(),
consolidate_protocols: Mix.env() != :test
]
end
@ -21,9 +30,21 @@ defmodule Wafer.MixProject do
]
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.
defp deps do
[
{:ex_doc, ">= 0.0.0", only: :dev},
{:earmark, ">= 0.0.0", only: :dev},
{:mimic, "~> 1.1", only: :test},
{:credo, "~> 1.1", only: [:dev, :test], runtime: false},
{: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_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"},
"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_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"},
"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"},
"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
|> expect(:set_direction, 1, fn ref, direction ->
assert ref == conn.ref
assert direction == :in
assert direction == :input
:ok
end)