# SPDX-FileCopyrightText: 2018 Frank Hunleth, Mark Sebald, Matt Ludwigs # # SPDX-License-Identifier: Apache-2.0 defmodule Circuits.GPIO do @moduledoc """ Control GPIOs from Elixir If you're coming from Elixir/ALE, check out our [porting guide](PORTING.md). `Circuits.GPIO` works great with LEDs, buttons, many kinds of sensors, and simple control of motors. In general, if a device requires high speed transactions or has hard real-time constraints in its interactions, this is not the right library. For those devices, see if there's a Linux kernel driver. """ alias Circuits.GPIO.Nif @typedoc "A GPIO pin number. See your device's documentation for how these connect to wires" @type pin_number :: non_neg_integer() @typedoc "The GPIO direction (input or output)" @type pin_direction :: :input | :output @typedoc "GPIO logic value (low = 0 or high = 1)" @type value :: 0 | 1 @typedoc "Trigger edge for pin change notifications" @type trigger :: :rising | :falling | :both | :none @typedoc "Pull mode for platforms that support controllable pullups and pulldowns" @type pull_mode :: :not_set | :none | :pullup | :pulldown @typedoc "Options for open/3" @type open_option :: {:initial_value, value() | :not_set} | {:pull_mode, pull_mode()} # Public API @doc """ Open a GPIO for use. `pin` should be a valid GPIO pin number on the system and `pin_direction` should be `:input` or `:output`. If opening as an output, then be sure to set the `:initial_value` option if you need the set to be glitch free. Options: * :initial_value - Set to `:not_set`, `0` or `1` if this is an output. `:not_set` is the default. * :pull_mode - Set to `:not_set`, `:pullup`, `:pulldown`, or `:none` for an input pin. `:not_set` is the default. """ @spec open(pin_number(), pin_direction(), [open_option()]) :: {:ok, reference()} | {:error, any()} def open(pin_number, pin_direction, options \\ []) do check_open_options(options) value = Keyword.get(options, :initial_value, :not_set) pull_mode = Keyword.get(options, :pull_mode, :not_set) Nif.open(pin_number, pin_direction, value, pull_mode) end defp check_open_options([]), do: :ok defp check_open_options([{:initial_value, value} | rest]) when value in [:not_set, 0, 1] do check_open_options(rest) end defp check_open_options([{:pull_mode, value} | rest]) when value in [:not_set, :pullup, :pulldown, :none] do check_open_options(rest) end defp check_open_options([bad_option | _]) do raise ArgumentError.exception("Unsupported option to GPIO.open/3: #{inspect(bad_option)}") end @doc """ Release the resources associated with the GPIO. This is optional. The garbage collector will free GPIO resources that aren't in use, but this will free them sooner. """ @spec close(reference()) :: :ok def close(gpio) do Nif.close(gpio) end @doc """ Read the current value on a pin. """ @spec read(reference()) :: value() def read(gpio) do Nif.read(gpio) end @doc """ Set the value of a pin. The pin should be configured to an output for this to work. """ @spec write(reference(), value()) :: :ok def write(gpio, value) do Nif.write(gpio, value) end @doc """ Enable or disable pin value change notifications. The notifications are sent based on the trigger parameter: * :none - No notifications are sent * :rising - Send a notification when the pin changes from 0 to 1 * :falling - Send a notification when the pin changes from 1 to 0 * :both - Send a notification on all changes Available Options: * `suppress_glitches` - It is possible that the pin transitions to a value and back by the time that Circuits GPIO gets to process it. This controls whether a notification is sent. Set this to `false` to receive notifications. * `receiver` - Process which should receive the notifications. Defaults to the calling process (`self()`) Notifications look like: ``` {:circuits_gpio, pin_number, timestamp, value} ``` Where `pin_number` is the pin that changed values, `timestamp` is roughly when the transition occurred in nanoseconds since host system boot time, and `value` is the new value. NOTE: You will need to store the `Circuits.GPIO` reference somewhere (like your `GenServer`'s state) so that it doesn't get garbage collected. Event messages stop when it gets collected. If you only get one message and you are expecting more, this is likely the case. """ @spec set_interrupts(reference(), trigger(), list()) :: :ok | {:error, atom()} def set_interrupts(gpio, trigger, opts \\ []) do suppress_glitches = Keyword.get(opts, :suppress_glitches, true) receiver = case Keyword.get(opts, :receiver) do pid when is_pid(pid) -> pid name when is_atom(name) -> Process.whereis(name) || self() _ -> self() end Nif.set_interrupts(gpio, trigger, suppress_glitches, receiver) end @doc """ Change the direction of the pin. """ @spec set_direction(reference(), pin_direction()) :: :ok | {:error, atom()} def set_direction(gpio, pin_direction) do Nif.set_direction(gpio, pin_direction) end @doc """ Enable or disable internal pull-up or pull-down resistor to GPIO pin """ @spec set_pull_mode(reference(), pull_mode()) :: :ok | {:error, atom()} def set_pull_mode(gpio, pull_mode) do Nif.set_pull_mode(gpio, pull_mode) end @doc """ Get the GPIO pin number """ @spec pin(reference) :: pin_number def pin(gpio) do Nif.pin(gpio) end @doc """ Return info about the low level GPIO interface This may be helpful when debugging issues. """ @spec info() :: map() defdelegate info(), to: Nif end