mnishiguchi
Debouncing `:circuits_gpio` messages on button push
I detect the button push by using Circuits.GPIO.set_interrupts(gpio_ref, :rising); however it is too sensitive that I want to debounce it. I am thinking about setting a condition using the second data item in the message tuple {:circuits_gpio, 17, 1233456, 1} for controlling the sensitivity. Is it good? Is there a better way?
My target is Rpi4. My button is a 6mm mini button.
defmodule LedBlinker.Button do
use GenServer, restart: :temporary
@debounce_time 200_000_000
def start_link({gpio_pin, on_push_fn}) when is_number(gpio_pin) and is_function(on_push_fn) do
GenServer.start_link(__MODULE__, {gpio_pin, on_push_fn})
end
def init({gpio_pin, on_push_fn}) do
# Get a ref to the GPIO pin.
{:ok, gpio_ref} = Circuits.GPIO.open(gpio_pin, :input)
# Get messages every time the button is pushed.
Circuits.GPIO.set_interrupts(gpio_ref, :rising)
{
:ok,
%{
gpio_pin: gpio_pin,
gpio_ref: gpio_ref,
last_pushed_at: 0,
on_push_fn: on_push_fn
}
}
end
def handle_info({:circuits_gpio, _, at, 1} = message, state) do
%{last_pushed_at: last_pushed_at, on_push_fn: on_push_fn} = state
# Debounce the message.
if @debounce_time < at - last_pushed_at do
on_push_fn.(message)
end
{:noreply, %{state | last_pushed_at: at}}
end
end
Most Liked
dominicletz
Cool, I think you’ve found the best solution. Just in case though I wanted to provider another option. I’ve had a couple of cases where I needed debounce behaviour and implement a module:
if you add this module to your dependencies:
def deps do
[{:debouncer, "~> 0.1.3"}]
end
you can just do:
def handle_info({:circuits_gpio, _, at, 1}, %{on_push_fn: on_push_fn} = state) do
Debouncer.immediate(:circuits_gpio, fn -> on_push_fn.(at) end, 5_000)
{:noreply, state}
end
And it will ensure that the event identified by the first argument (:circuits_gpio) is not triggered more often than every 5_000 millisecond.
There is actually a range of different debounce behaviors I needed in some of my projects so the module implements four different functions. All of them take 3 argument: (event_key, function, timeout)
- apply() - executes an event after TIMEOUT and so creates a regular interval from frequent events
- immediate() - is the same as apply() but triggers also immediate on the first event
- immediate2() - executes the first event as well immediately but mutes all further events until after TIMEOUT
- delay() - every event delays the trigger by TIMEOUT, will issue an event only once there has been no event for TIMEOUT milliseconds
Or here in a text graph:
EVENT X1---X2------X3-------X4----------
TIMEOUT ----------|----------|----------|-
===============================================
apply() ----------X2---------X3---------X4
immediate() X1--------X2---------X3---------X4
immediate2() X1-----------X3-------------------
delay() --------------------------------X4
Hope this helps some readers.
mnishiguchi
Wow, nice! Thanks. I will definitely keep that in my toolbox.
ondrej-tucek
I’d use send_after & cancel_timer or sleep functions instead of if statement. For example: Process.send_after(self(), {:debounce, on_push_fn, msg}, @debounce_time) + handle_info for :debounce.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








