lib/atecc508a/transport/i2c.ex

defmodule ATECC508A.Transport.I2C do
  alias ATECC508A.Transport
  require Logger

  @moduledoc """
  Implementation for communicating with ATECC508A parts connected over I2C
  """

  @behaviour Transport

  @default_atecc508a_bus "i2c-1"
  @default_atecc508a_address 0x60

  @type instance :: module()

  @typedoc """
  I2C transports are specified by a bus name like "i2c-1" and
  an I2C address.
  """
  @type i2c_args :: [bus_name: String.t(), address: Circuits.I2C.address()]

  @doc """
  Initialize an I2C transport to an ATECC508A

  Options are:

  * `:bus_name` - "i2c-0", "i2c-1", etc. The default is "i2c-1" which frequently
    works on Raspberry Pis and Beaglebones
  * `:address` - This is the I2C address of the ATECC508A/608A. The default
    is 0x60. It is not common to change this address in Nerves devices.

  If a transport has already been created to the specified ATECC508A, that
  transport will be returned. All commands to the ATECC508A are serialized
  so that they don't interfere with each other.
  """
  @impl Transport
  @spec init(i2c_args()) :: {:ok, Transport.t()} | {:error, atom()}
  def init(args \\ []) do
    bus_name = Keyword.get(args, :bus_name, @default_atecc508a_bus)
    address = Keyword.get(args, :address, @default_atecc508a_address)
    name = process_name(bus_name, address)

    case ATECC508A.Transport.I2CSupervisor.start_child(bus_name, address, name) do
      {:ok, _pid} ->
        {:ok, {__MODULE__, name}}

      {:ok, _pid, _info} ->
        {:ok, {__MODULE__, name}}

      {:error, {:already_started, _pid}} ->
        {:ok, {__MODULE__, name}}

      {:error, _} = err ->
        err

      other_error ->
        {:error, other_error}
    end
  end

  @impl Transport
  @spec detected?(instance()) :: boolean()
  defdelegate detected?(instance), to: ATECC508A.Transport.I2CServer

  @impl Transport
  @spec request(instance(), binary(), non_neg_integer(), non_neg_integer()) ::
          {:error, atom()} | {:ok, binary()}
  defdelegate request(instance, payload, timeout, response_payload_len),
    to: ATECC508A.Transport.I2CServer

  @impl Transport
  @spec transaction(instance(), (fun() -> {:ok, any()} | {:error, atom()})) ::
          {:ok, any()} | {:error, atom()}
  defdelegate transaction(instance, callback), to: ATECC508A.Transport.I2CServer

  @impl Transport
  @spec info(instance()) :: map()
  defdelegate info(instance), to: ATECC508A.Transport.I2CServer

  defp process_name(bus_name, address) do
    Module.concat([ATECC508A.Transport.I2C, bus_name, to_string(address)])
  end
end