defmodule I2cServer do @moduledoc """ I2C Server wraps [`Circuits.I2C`](https://hexdocs.pm/circuits_i2c/readme.html) [reference](http://erlang.org/documentation/doc-6.0/doc/reference_manual/data_types.html#id67235) in a [`GenServer`](https://hexdocs.pm/elixir/GenServer.html), creating a separate process per [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) bus. """ use GenServer @type bus_registry :: I2cServer.Busbus_registry | :global | atom @type bus_name :: binary @type bus_address :: 0..127 @type init_arg :: [ bus_name: bus_name, bus_address: bus_address ] @doc """ Returns the module atom of currently-used bus registry. """ @spec bus_registry() :: bus_registry def bus_registry() do Application.get_env(:i2c_server, :bus_registry_module, I2cServer.BusRegistry) end @doc """ Replaces the currently-used bus registry with a different one. """ @spec set_bus_registry(bus_registry) :: :ok def set_bus_registry(bus_registry) do Application.put_env(:i2c_server, :bus_registry_module, bus_registry) end @spec start_link(init_arg) :: GenServer.on_start() def start_link(init_arg) do GenServer.start_link(__MODULE__, init_arg) end @doc """ Initiates a read transaction to the I2C device. """ @spec read(GenServer.server(), integer) :: any def read(server, read_count) when is_integer(read_count) do GenServer.call(server, {:read, read_count}) end @doc """ Writes data to the I2C device. """ @spec write(GenServer.server(), iodata) :: any def write(server, register_and_data) when is_binary(register_and_data) or is_list(register_and_data) do GenServer.call(server, {:write, register_and_data}) end @spec write(GenServer.server(), integer, binary | integer) :: any def write(server, register, data) when is_integer(register) and (is_binary(data) or is_integer(data)) do GenServer.call(server, {:write, register, data}) end @doc """ Writes data to an I2C device and then immediately issue a read. """ @spec write_read(GenServer.server(), binary | integer, integer) :: any def write_read(server, write_data, read_count) when is_binary(write_data) and is_integer(read_count) do GenServer.call(server, {:write_read, write_data, read_count}) end def write_read(server, register, read_count) when is_integer(register) and is_integer(read_count) do GenServer.call(server, {:write_read, <>, read_count}) end @doc """ Run multiple operations in series blocking the process in favor of consistency. """ @spec bulk(GenServer.server(), [ {:sleep, integer} | {atom, atom, list} | {:read, integer} | {:write, integer, iodata} | {:write, integer, integer, binary | integer} | {:write_read, binary | integer, integer} | function() ]) :: list def bulk(server, bulk_operations) when is_list(bulk_operations) do GenServer.call(server, {:bulk, bulk_operations}) end @impl GenServer def init(args) when is_list(args) do state = %{ bus_name: Keyword.fetch!(args, :bus_name), bus_address: Keyword.fetch!(args, :bus_address) } {:ok, state} end @impl GenServer def handle_call({:read, read_count}, _from, state) do %{bus_name: bus_name, bus_address: bus_address} = state pid = bus_server_process(bus_name) result = I2cServer.BusWorker.read(pid, bus_address, read_count) {:reply, result, state} end @impl GenServer def handle_call({:write, register_and_data}, _from, state) do %{bus_name: bus_name, bus_address: bus_address} = state pid = bus_server_process(bus_name) result = I2cServer.BusWorker.write(pid, bus_address, register_and_data) {:reply, result, state} end def handle_call({:write, register, data}, _from, state) do %{bus_name: bus_name, bus_address: bus_address} = state pid = bus_server_process(bus_name) result = I2cServer.BusWorker.write(pid, bus_address, register, data) {:reply, result, state} end def handle_call({:write_read, register, read_count}, _from, state) do %{bus_name: bus_name, bus_address: bus_address} = state pid = bus_server_process(bus_name) result = I2cServer.BusWorker.write_read(pid, bus_address, register, read_count) {:reply, result, state} end def handle_call({:bulk, bulk_operations}, _from, state) do %{bus_name: bus_name, bus_address: bus_address} = state pid = bus_server_process(bus_name) result = bulk_operations |> bulk_operations_to_funs |> Stream.map(fn f -> f.(pid, bus_address) end) |> Enum.to_list() {:reply, result, state} end defp bulk_operations_to_funs(bulk_operations) do Stream.map(bulk_operations, fn anon_fun when is_function(anon_fun) -> fn server, bus_address when is_pid(server) and is_integer(bus_address) -> anon_fun.(server, bus_address) end {:sleep, ms} when is_integer(ms) -> fn server, bus_address when is_pid(server) and is_integer(bus_address) -> Process.sleep(ms) end {mod, fun, args} when is_atom(mod) and is_atom(fun) and is_list(args) -> fn server, bus_address when is_pid(server) and is_integer(bus_address) -> apply(mod, fun, args) end fun_name_and_args when is_tuple(fun_name_and_args) -> [fun_name | args] = Tuple.to_list(fun_name_and_args) fn server, bus_address when is_pid(server) and is_integer(bus_address) -> apply( I2cServer.BusWorker, fun_name, [server, bus_address] ++ args ) end _ -> raise ArgumentError, """ A list entry must be tuple or function. """ end) end defp bus_server_process(bus_name) do I2cServer.BusSupervisor.server_process(bus_name) end end