From 13f79bf72ec5e729111a7f5a3d1de2f02b7c0af7 Mon Sep 17 00:00:00 2001 From: James Harton Date: Tue, 31 Dec 2019 18:54:46 +1300 Subject: [PATCH] Make all our protocol derivable. --- README.md | 9 +++ lib/wafer/chip.ex | 74 +++++++++++++++++++++- lib/wafer/conn.ex | 28 +++++++- lib/wafer/drivers/circuits_gpio.ex | 8 ++- lib/wafer/drivers/circuits_i2c.ex | 4 +- lib/wafer/drivers/circuits_spi.ex | 4 +- lib/wafer/drivers/elixir_ale_gpio.ex | 4 +- lib/wafer/drivers/elixir_ale_i2c.ex | 4 +- lib/wafer/drivers/elixir_ale_spi.ex | 4 +- lib/wafer/gpio.ex | 95 ++++++++++++++++++++++++++++ lib/wafer/guards.ex | 6 +- lib/wafer/i2c.ex | 75 ++++++++++++++++++++++ lib/wafer/registers.ex | 3 + lib/wafer/spi.ex | 54 ++++++++++++++++ mix.exs | 25 +++++++- mix.lock | 5 ++ test/chip_test.exs | 43 +++++++++++++ test/drivers/circuits_gpio_test.exs | 2 +- 18 files changed, 431 insertions(+), 16 deletions(-) create mode 100644 test/chip_test.exs diff --git a/README.md b/README.md index 6df842c..d33b32f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/wafer/chip.ex b/lib/wafer/chip.ex index 6ad8082..e6c5650 100644 --- a/lib/wafer/chip.ex +++ b/lib/wafer/chip.ex @@ -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 diff --git a/lib/wafer/conn.ex b/lib/wafer/conn.ex index 5d9e924..4c617cf 100644 --- a/lib/wafer/conn.ex +++ b/lib/wafer/conn.ex @@ -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] diff --git a/lib/wafer/drivers/circuits_gpio.ex b/lib/wafer/drivers/circuits_gpio.ex index 2c2f6eb..b7a216d 100644 --- a/lib/wafer/drivers/circuits_gpio.ex +++ b/lib/wafer/drivers/circuits_gpio.ex @@ -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 diff --git a/lib/wafer/drivers/circuits_i2c.ex b/lib/wafer/drivers/circuits_i2c.ex index 86f4ab0..3e295ff 100644 --- a/lib/wafer/drivers/circuits_i2c.ex +++ b/lib/wafer/drivers/circuits_i2c.ex @@ -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 diff --git a/lib/wafer/drivers/circuits_spi.ex b/lib/wafer/drivers/circuits_spi.ex index f418014..5058c56 100644 --- a/lib/wafer/drivers/circuits_spi.ex +++ b/lib/wafer/drivers/circuits_spi.ex @@ -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 diff --git a/lib/wafer/drivers/elixir_ale_gpio.ex b/lib/wafer/drivers/elixir_ale_gpio.ex index 2ce02d8..d78df8d 100644 --- a/lib/wafer/drivers/elixir_ale_gpio.ex +++ b/lib/wafer/drivers/elixir_ale_gpio.ex @@ -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 diff --git a/lib/wafer/drivers/elixir_ale_i2c.ex b/lib/wafer/drivers/elixir_ale_i2c.ex index 9205d64..b9cf430 100644 --- a/lib/wafer/drivers/elixir_ale_i2c.ex +++ b/lib/wafer/drivers/elixir_ale_i2c.ex @@ -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 diff --git a/lib/wafer/drivers/elixir_ale_spi.ex b/lib/wafer/drivers/elixir_ale_spi.ex index 73b4188..f8f50d6 100644 --- a/lib/wafer/drivers/elixir_ale_spi.ex +++ b/lib/wafer/drivers/elixir_ale_spi.ex @@ -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 diff --git a/lib/wafer/gpio.ex b/lib/wafer/gpio.ex index 51784e1..b1b4105 100644 --- a/lib/wafer/gpio.ex +++ b/lib/wafer/gpio.ex @@ -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 diff --git a/lib/wafer/guards.ex b/lib/wafer/guards.ex index 101e232..ee1fa7f 100644 --- a/lib/wafer/guards.ex +++ b/lib/wafer/guards.ex @@ -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 diff --git a/lib/wafer/i2c.ex b/lib/wafer/i2c.ex index 1151392..56ca117 100644 --- a/lib/wafer/i2c.ex +++ b/lib/wafer/i2c.ex @@ -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 diff --git a/lib/wafer/registers.ex b/lib/wafer/registers.ex index f9c519b..a18c9de 100644 --- a/lib/wafer/registers.ex +++ b/lib/wafer/registers.ex @@ -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 diff --git a/lib/wafer/spi.ex b/lib/wafer/spi.ex index daf2921..02f046c 100644 --- a/lib/wafer/spi.ex +++ b/lib/wafer/spi.ex @@ -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 diff --git a/mix.exs b/mix.exs index d9259f4..bce7f65 100644 --- a/mix.exs +++ b/mix.exs @@ -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 "], + 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}, diff --git a/mix.lock b/mix.lock index b9329c5..0d318c2 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, } diff --git a/test/chip_test.exs b/test/chip_test.exs new file mode 100644 index 0000000..e1d4a79 --- /dev/null +++ b/test/chip_test.exs @@ -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 diff --git a/test/drivers/circuits_gpio_test.exs b/test/drivers/circuits_gpio_test.exs index ecc1e9d..9604b94 100644 --- a/test/drivers/circuits_gpio_test.exs +++ b/test/drivers/circuits_gpio_test.exs @@ -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)