lib/arb.ex

defmodule Arb do
  @moduledoc """
  A NIF for controlling the ABACOM CH341A relay board.
  """

  use Rustler,
    otp_app: :arb,
    crate: :arb

  @port_definition [
    type: :non_neg_integer,
    doc: "The USB port to be used. Only necessary if multiple relay boards are connected."
  ]

  @verify_definition [
    type: :boolean,
    doc: "Configures whether the activation should be verified.",
    default: true
  ]

  @typedoc """
  The relays are labeled from 1 to 8 according to the 
  [data sheet](http://www.abacom-online.de/div/ABACOM_USB_LRB.pdf).
  """
  @type relay_id :: 1..8

  @doc """
  Activates the relays that correspond to the given a list of IDs. An empty
  list deactivates all relays.

  ## Options

  #{NimbleOptions.docs(port: @port_definition, verify: @verify_definition)}

  ## Examples

      iex> Arb.activate([1, 4, 7])
      :ok

  """
  @spec activate([relay_id], Keyword.t()) :: :ok | {:error, Arb.Error.t()}
  def activate(ids, opts \\ []) when is_list(ids) do
    opts = NimbleOptions.validate!(opts, port: @port_definition, verify: @verify_definition)
    __activate__(ids, opts[:verify], opts[:port]) |> to_ok()
  end

  @doc """
  Returns the ids of active relays.

  ## Options

  #{NimbleOptions.docs(port: @port_definition)}

  ## Examples

      iex> Arb.get_active()
      {:ok, [1, 3, 6]}

  """
  @spec get_active(Keyword.t()) :: {:ok, [relay_id]} | {:error, Arb.Error.t()}
  def get_active(opts \\ []) do
    opts = NimbleOptions.validate!(opts, port: @port_definition)
    __get_active__(opts[:port])
  end

  @doc """
  Resets the relay board.

  If, under some circumstances relay board operations fail due to a USB error
  e.g. `{:error, {:usb, "Input/Output Error"}}`, this function may resolve
  the issue by resetting the relay board. The effect is similar to replugging
  the device.

  **Note:** Previously activated relays stay active.

  ## Options

  #{NimbleOptions.docs(port: @port_definition)}

  ## Examples

      iex> Arb.reset()
      :ok

  """
  @spec reset(Keyword.t()) :: {:ok, [relay_id]} | {:error, Arb.Error.t()}
  def reset(opts \\ []) do
    opts = NimbleOptions.validate!(opts, port: @port_definition)
    __reset__(opts[:port]) |> to_ok()
  end

  defp __activate__(_ids, _verify, _port), do: :erlang.nif_error(:nif_not_loaded)
  defp __get_active__(_port), do: :erlang.nif_error(:nif_not_loaded)
  defp __reset__(_port), do: :erlang.nif_error(:nif_not_loaded)

  defp to_ok({:ok, {}}), do: :ok
  defp to_ok(other), do: other
end