From a1b5513c1116cc9acc69107fd80b00e03d897bca Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 8 Jan 2020 11:31:15 +1300 Subject: [PATCH] Improve the documentation of the `defregister` macro. --- lib/wafer/registers.ex | 151 ++++++++++++++++-- .../drivers/circuits_gpio_dispatcher_test.exs | 2 +- test/drivers/circuits_gpio_test.exs | 9 +- test/drivers/circuits_i2c_test.exs | 2 +- test/drivers/circuits_spi_test.exs | 2 +- .../elixir_ale_gpio_dispatcher_test.exs | 2 +- test/drivers/elixir_ale_gpio_test.exs | 2 +- test/drivers/elixir_ale_i2c_test.exs | 2 +- test/drivers/elixir_ale_spi_test.exs | 2 +- 9 files changed, 150 insertions(+), 24 deletions(-) diff --git a/lib/wafer/registers.ex b/lib/wafer/registers.ex index a18c9de..f8cee3f 100644 --- a/lib/wafer/registers.ex +++ b/lib/wafer/registers.ex @@ -6,6 +6,8 @@ defmodule Wafer.Registers do This can be a massive time saver, and means you can basically just copy them straight out of the datasheet. + + See the documentation for `defregister/4` for more information. """ alias Wafer.Chip alias Wafer.Conn @@ -20,8 +22,8 @@ defmodule Wafer.Registers do end end - @doc """ - Define a registers. + @doc ~S""" + Define functions for interacting with a device register. ## Parameters - `name` - name of the register. @@ -31,17 +33,84 @@ defmodule Wafer.Registers do ## Examples + ### Read-only registers + + Define a read-only register named `status` at address `0x03` which is a single + byte wide: + iex> defregister(:status, 0x03, :ro, 1) - iex> defregister(:config, 0x01, :rw, 2) + This will define the following function along with documentation and + typespecs: - iex> defregister(:int_en, 0x02, :wo, 1) + ```elixir + def read_status(conn), do: Chip.read_register(conn, 0x03, 1) + ``` + + ### Write-only registers + + Define a write-only register named `int_en` at address `0x02` which is 2 bytes + wide: + + iex> defregister(:int_en, 0x02, :wo, 2) + + This will define the following functions along with documentation and + typespecs: + + ```elixir + def write_int_en(conn, data) + when is_binary(data) and byte_size(data) == 2, + do: Chip.write_register(conn, 0x2, data) + + def write_int_en(_conn, data), + do: {:error, "Argument error: #{inspect(data)}"} + ``` + + ### Read-write registers. + + Define a read-write register named `config` at address `0x01`. + + iex> defregister(:config, 0x01, :rw, 1) + + In addition to defining `read_config/1` and `write_config/2` as per the + examples above it will also generate the following functions along with + documentation and typespecs: + + ```elixir + def swap_config(conn, data) + when is_binary(data) and byte_size(data) == 1, + do: Chip.swap_register(conn, 0x01, data) + + def swap_config(_conn, data), + do: {:error, "Argument error: #{inspect(data)}"} + + def update_config(conn, callback) + when is_function(callback, 1) do + with {:ok, data} <- Chip.read_register(conn, 0x01, 1), + new_data when is_binary(new_data) and byte_size(new_data) == 1 <- callback.(data), + {:ok, conn} <- Chip.write_regsiter(conn, 0x01, new_data), + do: {:ok, conn} + end + + def update_config(_conn, _callback), + do: {:error, "Argument error: callback should be an arity 1 function"} + ``` """ defmacro defregister(name, register_address, :ro, bytes) when is_atom(name) and is_integer(register_address) and register_address >= 0 and is_integer(bytes) and bytes >= 0 do + empty_bytes = 1..bytes |> Enum.map(fn _ -> 0 end) |> Enum.join(", ") + quote do + @doc """ + Read the contents of the `#{unquote(name)}` register. + + ## Example + + iex> read_#{unquote(name)}(conn) + {:ok, <<#{unquote(empty_bytes)}>>} + """ @spec unquote(:"read_#{name}")(Conn.t()) :: {:ok, binary} | {:error, reason :: any} def unquote(:"read_#{name}")(conn), do: Chip.read_register(conn, unquote(register_address), unquote(bytes)) @@ -51,8 +120,19 @@ defmodule Wafer.Registers do defmacro defregister(name, register_address, :wo, bytes) when is_atom(name) and is_integer(register_address) and register_address >= 0 and is_integer(bytes) and bytes >= 0 do + empty_bytes = 1..bytes |> Enum.map(fn _ -> 0 end) |> Enum.join(", ") + quote do - @spec unquote(:"write_#{name}")(Conn.t(), data :: binary) :: :ok | {:error, reason :: any} + @doc """ + Write new contents to the `#{unquote(name)}` register. + + ## Example + + iex> write_#{unquote(name)}(conn, <<#{unquote(empty_bytes)}>>) + {:ok, _conn} + """ + @spec unquote(:"write_#{name}")(Conn.t(), data :: binary) :: + {:ok, Conn.t()} | {:error, reason :: any} def unquote(:"write_#{name}")(conn, data) when is_binary(data) and byte_size(data) == unquote(bytes), do: Chip.write_register(conn, unquote(register_address), data) @@ -64,44 +144,89 @@ defmodule Wafer.Registers do defmacro defregister(name, register_address, :rw, bytes) when is_atom(name) and is_integer(register_address) and register_address >= 0 and is_integer(bytes) and bytes >= 0 do + empty_bytes = 1..bytes |> Enum.map(fn _ -> 0 end) |> Enum.join(", ") + bits = bytes * 8 + quote do + @doc """ + Read the contents of the `#{unquote(name)}` register. + + ## Example + + iex> read_#{unquote(name)}(conn) + {:ok, <<#{unquote(empty_bytes)}>>} + """ @spec unquote(:"read_#{name}")(Conn.t()) :: {:ok, binary} | {:error, reason :: any} def unquote(:"read_#{name}")(conn), do: Chip.read_register(conn, unquote(register_address), unquote(bytes)) - @spec unquote(:"write_#{name}")(Conn.t(), data :: binary) :: :ok | {:error, reason :: any} + @doc """ + Write new contents to the `#{unquote(name)}` register. + + ## Example + + iex> write_#{unquote(name)}(conn, <<#{unquote(empty_bytes)}>>) + {:ok, _conn} + """ + @spec unquote(:"write_#{name}")(Conn.t(), data :: binary) :: + {:ok, Conn.t()} | {:error, reason :: any} def unquote(:"write_#{name}")(conn, data) when is_binary(data) and byte_size(data) == unquote(bytes), do: Chip.write_register(conn, unquote(register_address), data) def unquote(:"write_#{name}")(_conn, data), do: {:error, "Argument error: #{inspect(data)}"} + @doc """ + Swap the contents of the `#{unquote(name)}` register. + + Reads the contents of the register, then replaces it, returning the + previous contents. Some drivers may implement this atomically. + + ## Example + + iex> swap_#{unquote(name)}(conn, <<#{unquote(empty_bytes)}>>) + {:ok, <<#{unquote(empty_bytes)}>>, _conn} + """ @spec unquote(:"swap_#{name}")(Conn.t(), data :: binary) :: - :ok | {:error, reason :: any} + {:ok, Conn.t()} | {:error, reason :: any} def unquote(:"swap_#{name}")(conn, data) when is_binary(data) and byte_size(data) == unquote(bytes), do: Chip.swap_register(conn, unquote(register_address), data) + def unquote(:"swap_#{name}")(_conn, data), do: {:error, "Argument error: #{inspect(data)}"} + + @doc """ + Update the contents of the `#{unquote(name)}` register using a + transformation function. + + ## Example + + iex> transform = fn <> -> <<(data * 2)::size(#{ + unquote(bits) + })>> end + ...> update_#{unquote(name)}(conn, transform) + {:ok, _conn} + """ @spec unquote(:"update_#{name}")( Conn.t(), - (<<_::_*unquote(bytes * 8)>> -> <<_::_*unquote(bytes * 8)>>) - ) :: :ok | {:error, reason :: any} + (<<_::_*unquote(bits)>> -> <<_::_*unquote(bits)>>) + ) :: {:ok, Conn.t()} | {:error, reason :: any} def unquote(:"update_#{name}")(conn, callback) when is_function(callback, 1) do with {:ok, old_data} <- Chip.read_register(conn, unquote(register_address), unquote(bytes)), new_data when is_binary(new_data) and byte_size(new_data) == unquote(bytes) <- callback.(old_data), - :ok <- Chip.write_register(conn, unquote(register_address), new_data), - do: :ok + {:ok, conn} <- Chip.write_register(conn, unquote(register_address), new_data), + do: {:ok, conn} end - def unquote(:"update_#{name}")(_conn, callback), + def unquote(:"update_#{name}")(_conn, _callback), do: {:error, "Argument error: callback should be an arity 1 function"} end end @doc """ - Define a register with common defaults: + Define functions for interacting with a device register with common defaults. ## Examples diff --git a/test/drivers/circuits_gpio_dispatcher_test.exs b/test/drivers/circuits_gpio_dispatcher_test.exs index da20eb7..1674cbe 100644 --- a/test/drivers/circuits_gpio_dispatcher_test.exs +++ b/test/drivers/circuits_gpio_dispatcher_test.exs @@ -1,7 +1,7 @@ defmodule WaferDriverCircuits.GPIO.DispatcherTest do use ExUnit.Case, async: true - alias Wafer.Driver.Circuits.GPIO.Wrapper alias Wafer.Driver.Circuits.GPIO.Dispatcher, as: Dispatcher + alias Wafer.Driver.Circuits.GPIO.Wrapper alias Wafer.InterruptRegistry, as: IR import Mimic @moduledoc false diff --git a/test/drivers/circuits_gpio_test.exs b/test/drivers/circuits_gpio_test.exs index 3458118..128d7cc 100644 --- a/test/drivers/circuits_gpio_test.exs +++ b/test/drivers/circuits_gpio_test.exs @@ -1,9 +1,9 @@ defmodule WaferDriverCircuits.GPIOTest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.Circuits.GPIO.Wrapper alias Wafer.Driver.Circuits.GPIO, as: Subject alias Wafer.Driver.Circuits.GPIO.Dispatcher, as: Dispatcher + alias Wafer.Driver.Circuits.GPIO.Wrapper alias Wafer.GPIO, as: GPIO @moduledoc false @@ -74,7 +74,7 @@ defmodule WaferDriverCircuits.GPIOTest do |> reject(:set_direction, 2) assert {:ok, %Subject{} = conn} = Subject.acquire(pin: 1, direction: :out) - assert {:ok, %Subject{}} = GPIO.direction(conn, :out) + assert {:ok, ^conn} = GPIO.direction(conn, :out) end test "when the direction is changing" do @@ -87,7 +87,8 @@ defmodule WaferDriverCircuits.GPIOTest do :ok end) - assert {:ok, %Subject{}} = GPIO.direction(conn, :in) + assert {:ok, conn} = GPIO.direction(conn, :in) + assert conn.direction == :in end end @@ -132,7 +133,7 @@ defmodule WaferDriverCircuits.GPIOTest do :ok end) - assert {:ok, %Subject{}} = GPIO.pull_mode(conn, :pull_up) + assert {:ok, ^conn} = GPIO.pull_mode(conn, :pull_up) end end diff --git a/test/drivers/circuits_i2c_test.exs b/test/drivers/circuits_i2c_test.exs index cf0bcb9..f0f45d3 100644 --- a/test/drivers/circuits_i2c_test.exs +++ b/test/drivers/circuits_i2c_test.exs @@ -1,9 +1,9 @@ defmodule WaferCircuits.I2CTest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.Circuits.I2C.Wrapper alias Wafer.Chip alias Wafer.Driver.Circuits.I2C, as: Subject + alias Wafer.Driver.Circuits.I2C.Wrapper alias Wafer.I2C @moduledoc false diff --git a/test/drivers/circuits_spi_test.exs b/test/drivers/circuits_spi_test.exs index af45f2b..3778501 100644 --- a/test/drivers/circuits_spi_test.exs +++ b/test/drivers/circuits_spi_test.exs @@ -1,8 +1,8 @@ defmodule WaferCircuits.SPITest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.Circuits.SPI.Wrapper alias Wafer.Driver.Circuits.SPI, as: Subject + alias Wafer.Driver.Circuits.SPI.Wrapper alias Wafer.SPI @moduledoc false diff --git a/test/drivers/elixir_ale_gpio_dispatcher_test.exs b/test/drivers/elixir_ale_gpio_dispatcher_test.exs index 1bc1dae..e7b4ff5 100644 --- a/test/drivers/elixir_ale_gpio_dispatcher_test.exs +++ b/test/drivers/elixir_ale_gpio_dispatcher_test.exs @@ -1,7 +1,7 @@ defmodule WaferDriverElixirALE.GPIO.DispatcherTest do use ExUnit.Case, async: true - alias Wafer.Driver.ElixirALE.GPIO.Wrapper alias Wafer.Driver.ElixirALE.GPIO.Dispatcher, as: Dispatcher + alias Wafer.Driver.ElixirALE.GPIO.Wrapper alias Wafer.InterruptRegistry, as: IR import Mimic @moduledoc false diff --git a/test/drivers/elixir_ale_gpio_test.exs b/test/drivers/elixir_ale_gpio_test.exs index 04e015d..d0bf3e1 100644 --- a/test/drivers/elixir_ale_gpio_test.exs +++ b/test/drivers/elixir_ale_gpio_test.exs @@ -1,9 +1,9 @@ defmodule WaferDriverElixirALE.GPIOTest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.ElixirALE.GPIO.Wrapper 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 @moduledoc false diff --git a/test/drivers/elixir_ale_i2c_test.exs b/test/drivers/elixir_ale_i2c_test.exs index 5ea0799..c518ebd 100644 --- a/test/drivers/elixir_ale_i2c_test.exs +++ b/test/drivers/elixir_ale_i2c_test.exs @@ -1,9 +1,9 @@ defmodule WaferElixirALE.I2CTest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.ElixirALE.I2C.Wrapper alias Wafer.Chip alias Wafer.Driver.ElixirALE.I2C, as: Subject + alias Wafer.Driver.ElixirALE.I2C.Wrapper alias Wafer.I2C @moduledoc false diff --git a/test/drivers/elixir_ale_spi_test.exs b/test/drivers/elixir_ale_spi_test.exs index b60864b..09e748e 100644 --- a/test/drivers/elixir_ale_spi_test.exs +++ b/test/drivers/elixir_ale_spi_test.exs @@ -1,8 +1,8 @@ defmodule WaferElixirALE.SPITest do use ExUnit.Case, async: true use Mimic - alias Wafer.Driver.ElixirALE.SPI.Wrapper alias Wafer.Driver.ElixirALE.SPI, as: Subject + alias Wafer.Driver.ElixirALE.SPI.Wrapper alias Wafer.SPI @moduledoc false