lib/wafer/drivers/circuits/gpio.ex

defmodule Wafer.Driver.Circuits.GPIO do
  defstruct ~w[direction pin ref]a
  @behaviour Wafer.Conn
  alias Wafer.Driver.Circuits.GPIO.Wrapper
  alias Wafer.GPIO
  import Wafer.Guards

  @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()}

  @type options :: [option]
  @type option :: {:pin, non_neg_integer} | {:direction, GPIO.pin_direction()}

  @doc """
  Acquire a connection to a native GPIO pin via Circuit's GPIO driver.

  ## Options

    - `:pin` (required) the integer number of the pin to connect to.  Hardware dependent.
    - `:direction` (optional) 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 is_pin_direction(direction) <- Keyword.get(opts, :direction, :out),
         pin_dir <- String.to_atom(Enum.join([direction, "put"], "")),
         {:ok, ref} <- Wrapper.open(pin, pin_dir, Keyword.drop(opts, ~w[pin direction]a)) do
      {:ok, %__MODULE__{ref: ref, pin: pin, direction: direction}}
    else
      :error -> {:error, "Circuits.GPIO requires a `pin` option."}
      {:error, reason} -> {:error, reason}
    end
  end
end

defimpl Wafer.Release, for: Wafer.Driver.Circuits.GPIO do
  alias Wafer.Driver.Circuits.GPIO.Wrapper
  alias Wafer.Driver.Circuits.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{ref: ref} = _conn) when is_reference(ref), do: Wrapper.close(ref)
end

defimpl Wafer.GPIO, for: Wafer.Driver.Circuits.GPIO do
  alias Wafer.Driver.Circuits.GPIO.Dispatcher
  alias Wafer.Driver.Circuits.GPIO.Wrapper
  import Wafer.Guards

  def read(%{ref: ref}) when is_reference(ref) do
    case(Wrapper.read(ref)) 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(%{ref: ref} = conn, value) when is_reference(ref) and is_pin_value(value) do
    with :ok <- Wrapper.write(ref, value), do: {:ok, conn}
  end

  def direction(%{direction: :in} = conn, :in), do: {:ok, conn}
  def direction(%{direction: :out} = conn, :out), do: {:ok, conn}

  def direction(%{ref: ref} = conn, direction)
      when is_reference(ref) and is_pin_direction(direction) do
    with pin_dir <- translate_pin_direction(direction),
         :ok <- Wrapper.set_direction(ref, pin_dir),
         do: {:ok, %{conn | direction: direction}}
  end

  def enable_interrupt(conn, pin_condition, metadata \\ nil)
      when is_pin_condition(pin_condition) do
    with :ok <- Dispatcher.enable(conn, pin_condition, metadata), do: {:ok, conn}
  end

  def disable_interrupt(conn, pin_condition) when is_pin_condition(pin_condition) do
    with :ok <- Dispatcher.disable(conn, pin_condition), do: {:ok, conn}
  end

  def pull_mode(%{ref: ref} = conn, mode) when is_reference(ref) and is_pin_pull_mode(mode) do
    with :ok <- Wrapper.set_pull_mode(ref, mode), do: {:ok, conn}
  end

  defp translate_pin_direction(:in), do: :input
  defp translate_pin_direction(:out), do: :output
end

defimpl Wafer.DeviceID, for: Wafer.Driver.Circuits.GPIO do
  def id(%{pin: pin}), do: {Wafer.Driver.Circuits.GPIO, pin}
end