Packages

Wafer is an Elixir library to make writing drivers for i2c and SPI connected peripherals and interacting with GPIO pins easier.

Current section

Files

Jump to
wafer lib wafer interrupt_registry.ex
Raw

lib/wafer/interrupt_registry.ex

defmodule Wafer.InterruptRegistry do
alias Wafer.{Conn, GPIO}
alias __MODULE__, as: IR
import Wafer.Guards
@moduledoc """
This module provides Wafer's interrupt registry. This allows multiple
subscribers to be subscribed to interrupts from many different pins.
It is used by `Driver.Circuits.GPIO.Dispatcher` and
`Driver.ElixirALE.GPIO.Dispatcher` and you should probably use it if you're
writing your own driver which supports sending interrupts to subscribers.
"""
@type key :: any
@doc """
Subscribe the calling process to interrupts for the specified `pin_condition`
using the provided `key`.
See `subscribe/4` for more information.
## Example
iex> subscribe({MCP230017.Pin, 13}, :rising, conn)
:ok
"""
@spec subscribe(key, GPIO.pin_condition(), Conn.t()) :: :ok
def subscribe(key, pin_condition, conn)
when is_pin_condition(pin_condition),
do: subscribe(key, pin_condition, conn, nil)
@doc """
Subscribe the calling process to interrupts for the specified `pin_condition`
using the provided `key`.
## Arguments
- `key` a key which uniquely describes the pin. Probably a combination of
device name and pin.
- `pin_condition` either `:rising`, `:falling` or `:both`.
- `conn` the receiver's connection to the pin, sent back to them in the
interrupt message.
- `metadata` arbitrary data which will be sent to the receiver process in
the interrupt message. Defaults to `nil`.
"""
@spec subscribe(key, GPIO.pin_condition(), Conn.t(), any) :: :ok
def subscribe(key, :rising = _pin_condition, conn, metadata) do
with :yes <- Registry.register_name({IR, key, {:rising, conn, metadata}}, self()), do: :ok
end
def subscribe(key, :falling = _pin_condition, conn, metadata) do
with :yes <- Registry.register_name({IR, key, {:falling, conn, metadata}}, self()), do: :ok
end
def subscribe(key, :both = _pin_condition, conn, metadata) do
with :yes <- Registry.register_name({IR, key, {:both, conn, metadata}}, self()), do: :ok
end
@doc """
Remove all subscriptions for `key`, `pin_condition` and `conn`.
"""
@spec unsubscribe(key, GPIO.pin_condition(), Conn.t()) :: :ok
def unsubscribe(key, :rising = _pin_condition, conn),
do: Registry.unregister_match(IR, key, {:rising, conn, :_})
def unsubscribe(key, :falling = _pin_condition, conn),
do: Registry.unregister_match(IR, key, {:falling, conn, :_})
def unsubscribe(key, :both = _pin_condition, conn),
do: Registry.unregister_match(IR, key, {:both, conn, :_})
@doc """
Returns a list of all subscriptions to `key`.
"""
@spec subscriptions(key) :: [{GPIO.pin_condition(), Conn.t(), metadata :: any, receiver :: pid}]
def subscriptions(key) do
IR
|> Registry.match(key, :"$1")
|> Enum.map(fn {pid, {condition, conn, metadata}} -> {condition, conn, metadata, pid} end)
end
@doc """
Returns a list of all subscriptions `key` and `pin_condition`.
"""
@spec subscriptions(key, GPIO.pin_condition()) :: [
{GPIO.pin_condition(), Conn.t(), metadata :: any, receiver :: pid}
]
def subscriptions(key, pin_condition) when is_pin_condition(pin_condition) do
IR
|> Registry.match(key, {pin_condition, :"$1", :"$2"})
|> Enum.map(fn {pid, {condition, conn, metadata}} -> {condition, conn, metadata, pid} end)
end
@doc """
Count the number of active subscriptions for `key`.
"""
@spec count_subscriptions(key) :: non_neg_integer
def count_subscriptions(key), do: Registry.count_match(IR, key, :_)
@doc """
Count the number of active subscriptions for `key` and `pin_condition`.
"""
@spec count_subscriptions(key, GPIO.pin_condition()) :: non_neg_integer
def count_subscriptions(key, pin_condition) when is_pin_condition(pin_condition),
do: Registry.count_match(IR, key, {pin_condition, :_, :_})
@doc """
Are there any subscribers to `key`?
"""
@spec subscribers?(key) :: boolean
def subscribers?(key), do: count_subscriptions(key) > 0
@doc """
Are there any subscribers to `key` and `pin_condition`?
"""
@spec subscribers?(key, GPIO.pin_condition()) :: boolean
def subscribers?(key, pin_condition) when is_pin_condition(pin_condition),
do: count_subscriptions(key, pin_condition) > 0
@doc """
Publish an interrupt to subscribers that are interested in `key` and
`pin_condition`.
Searches the registry for all subscribers to are subscribed to `pin_condition`
or `:both` and publishes the interrupt message to them. The interrupt message
takes the form `{:interrupt, Conn.t(), :rising | :falling, metadata :: any}`.
"""
@spec publish(key, :rising | :falling) :: {:ok, non_neg_integer}
def publish(key, pin_condition) when pin_condition in ~w[rising falling]a do
count =
IR
|> Registry.match(key, {:"$1", :"$2", :"$3"}, [
{:or, {:==, :"$1", pin_condition}, {:==, :"$1", :both}}
])
|> Enum.map(fn {pid, {_condition, conn, metadata}} ->
send(pid, {:interrupt, conn, pin_condition, metadata})
end)
|> Enum.count()
{:ok, count}
end
end