I am trying to monitor a GPIO on the Raspberry Pi. The use case is to build a shutdown button which will turn off the device if the button is pressed for some time (e.g. half a second).
I am pretty sure this can be done with the Circuits.GPIO library. But can I use any pin? Specifically, I wonder if I can use GPIO 3 (Pin #5) on the RPi 4. The pin is also called SCL1 and is normally used for I2C. I don’t use I2C on my system, so does that mean it’s safe to use as a GPIO?
I have already experimented a little on my Nerves system. I can SSH into the box and open the GPIO, and then I get events in the process mailbox like this:
iex(5)> {:ok, gpio} = Circuits.GPIO.open(3, :input)
{:ok, #Reference<0.1860275132.899809292.10759>}
iex(6)> :ok = Circuits.GPIO.set_interrupts(gpio, :both)
:ok
iex(7)> flush
{:circuits_gpio, 3, 190486901208, 1}
:ok
iex(8)> flush
{:circuits_gpio, 3, 194963466058, 0}
{:circuits_gpio, 3, 195712390258, 1}
:ok
iex(9)> flush
{:circuits_gpio, 3, 196928666676, 0}
{:circuits_gpio, 3, 197254295893, 1}
So that’s great. But I wrote a GenServer to monitor the button presses, and it seems I am not getting the messages. Sometimes I get the first message which has the initial state, but sometimes not. I think it’s probably a really basic mistake, maybe someone can spot it?
defmodule PoweroffDaemon do
# When the button goes low, start a timer.
# If it does not go high before timer ticks, shut down.
# If it goes high before the timer ticks, cancel the timer.
use GenServer, restart: :temporary
require Logger
defstruct [:timer, :click_ref]
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts)
end
@impl GenServer
def init(_) do
IO.puts("starting server")
{:ok, gpio} = Circuits.GPIO.open(3, :input)
:ok = Circuits.GPIO.set_interrupts(gpio, :both)
{:ok, %__MODULE__{}}
end
@impl GenServer
def handle_info({:circuits_gpio, 3, _ts, 0}, state) do
IO.puts("button went low")
click_ref = make_ref()
timer = Process.send_after(self(), {:shutdown, click_ref}, 500)
{:noreply, %{state | timer: timer, click_ref: click_ref}}
end
def handle_info({:circuits_gpio, 3, _ts, 1}, %{timer: nil} = state) do
IO.puts("ignoring initial gpio high event")
{:noreply, state}
end
def handle_info({:circuits_gpio, 3, _ts, 1}, state) do
IO.puts("button went high")
Process.cancel_timer(state.timer)
{:noreply, %{state | timer: nil, click_ref: nil}}
end
def handle_info({:shutdown, click_ref}, %{click_ref: click_ref} = state) do
IO.puts("SHUTDOWN!!!")
Nerves.Runtime.poweroff()
{:noreply, state}
end
def handle_info({:shutdown, _click_ref}, state) do
IO.puts("ignoring cancelled shutdown timer")
{:noreply, state}
end
def handle_info(msg, state) do
IO.puts("unhandled msg msg=#{inspect(msg)}, state=#{inspect(state)}")
{:noreply, state}
end
end