Current section
Files
Jump to
Current section
Files
lib/wafer/gpio.ex
defprotocol Wafer.GPIO do
alias Wafer.Conn
@moduledoc """
A `GPIO` is a physical pin which can be read from and written to.
Some hardware supports interrupts, some has internal pull up/down resistors.
Wafer supports all of these, however not all drivers do.
## Deriving
If you're implementing your own `Conn` type that simply delegates to one of
the lower level drivers then you can derive this protocol automatically:
```elixir
defstruct MyPin do
@derive Wafer.GPIO
defstruct [:conn]
end
```
If your type uses a key other than `conn` for the inner connection you can
specify it while deriving:
```elixir
defstruct MyPin do
@derive {Wafer.GPIO, key: :gpio_conn}
defstruct [:gpio_conn]
end
```
"""
@type pin_number :: non_neg_integer
@type pin_direction :: :in | :out
@type pin_condition :: :none | :rising | :falling | :both
@type pin_value :: 0 | 1
@type pull_mode :: :not_set | :none | :pull_up | :pull_down
@type interrupt_options :: [interrupt_option]
@type interrupt_option :: {:suppress_glitches, boolean} | {:receiver, pid}
@doc """
Read the current pin value.
"""
@spec read(Conn.t()) :: {:ok, pin_value, Conn.t()} | {:error, reason :: any}
def read(conn)
@doc """
Set the pin value.
"""
@spec write(Conn.t(), pin_value) :: {:ok, Conn.t()} | {:error, reason :: any}
def write(conn, pin_value)
@doc """
Set the pin direction.
"""
@spec direction(Conn.t(), pin_direction) :: {:ok, Conn.t()} | {:error, reason :: any}
def direction(conn, pin_direction)
@doc """
Enable an interrupt for this connection and pin_condition.
## Arguments
- `pin_condition` - Either `:rising` or `:falling`.
- `metadata` - any optional data you wish to receive along with your
interrupts. Defaults to `nil`.
Interrupts will be sent to the calling process as messages in the form of
`{:interrupt, Conn.t(), pin_condition, metadata | nil}`.
## Implementors note
`Wafer` starts it's own `Registry` named `Wafer.InterruptRegistry` which you
can use to publish your interrupts to using the above format. The registry
key is set as follows: `{PublishingModule, pin, pin_condition}`. You can see
examples in the `Circuits.GPIO.Dispatcher` and `ElixirALE.GPIO.Dispatcher`
modules.
"""
@spec enable_interrupt(Conn.t(), pin_condition, any) ::
{:ok, Conn.t()} | {:error, reason :: any}
def enable_interrupt(conn, pin_condition, metadata \\ nil)
@doc """
Disables interrupts for this connection and pin_condition.
"""
@spec disable_interrupt(Conn.t(), pin_condition) :: {:ok, Conn.t()} | {:error, reason :: any}
def disable_interrupt(conn, pin_condition)
@doc """
Set the pull mode for this pin.
If the hardware contains software-switchable pull-up and/or pull-down
resistors you can configure them this way. If they are not supported then
this function will return `{:error, :not_supported}`.
"""
@spec pull_mode(Conn.t(), pull_mode) :: {:ok, Conn.t()} | {:error, reason :: any}
def pull_mode(conn, pull_mode)
end
defimpl Wafer.GPIO, for: Any do
defmacro __deriving__(module, struct, options) do
key = Keyword.get(options, :key, :conn)
unless Map.has_key?(struct, key) do
raise(
"Unable to derive `Wafer.GPIO` for `#{module}`: key `#{inspect(key)}` not present in struct."
)
end
quote do
defimpl Wafer.GPIO, for: unquote(module) do
import Wafer.Guards
alias Wafer.GPIO
def read(%{unquote(key) => inner_conn} = conn) do
with {:ok, pin_value, inner_conn} <- GPIO.read(inner_conn),
do: {:ok, pin_value, Map.put(conn, unquote(key), inner_conn)}
end
def write(%{unquote(key) => inner_conn} = conn, pin_value) when is_pin_value(pin_value) do
with {:ok, inner_conn} <- GPIO.write(inner_conn, pin_value),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def direction(%{unquote(key) => inner_conn} = conn, pin_direction)
when is_pin_direction(pin_direction) do
with {:ok, inner_conn} <- GPIO.direction(inner_conn, pin_direction),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def enable_interrupt(%{unquote(key) => inner_conn} = conn, pin_condition, metadata \\ nil)
when is_pin_condition(pin_condition) do
with {:ok, inner_conn} <- GPIO.enable_interrupt(inner_conn, pin_condition, metadata),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def disable_interrupt(%{unquote(key) => inner_conn} = conn, pin_condition)
when is_pin_condition(pin_condition) do
with {:ok, inner_conn} <- GPIO.disable_interrupt(inner_conn, pin_condition),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
def pull_mode(%{unquote(key) => inner_conn} = conn, pull_mode)
when is_pin_pull_mode(pull_mode) do
with {:ok, inner_conn} <- GPIO.pull_mode(inner_conn, pull_mode),
do: {:ok, Map.put(conn, unquote(key), inner_conn)}
end
end
end
end
def read(unknown), do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def write(unknown, _pin_value),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def direction(unknown, _pin_direction),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}"}
def enable_interrupt(unknown, _pin_condition, _metadata \\ nil),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
def disable_interrupt(unknown, _pin_condition),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
def pull_mode(unknown, _pull_mode),
do: {:error, "`Wafer.GPIO` not implemented for `#{inspect(unknown)}`"}
end