Over the New Year holiday I worked on a little Nerves project to help my daughter with her morse skills. It enables text to be sent to Slack, via a morse telegraph key and a Raspberry Pi Zero W. (The project is here.)

One of the issues to overcome was contact bounce on a key down. Several events showing the circuit opening and closing in quick succession would be sent by the Elixir Circuits library each time the key was depressed.

Rather than debounce with an electrical circuit I decided to do it with an OTP GenServer, which initially looked like this:

defmodule Telegraph.Debounce do
  @moduledoc """
  Remove the bounce caused by closing the telegraph key. Doing it in
  software so we don't have to mess with capacitors. Ignores events that occur
  within 40ms of each other. Sends events that are not followed by another after 40 milliseconds.


  See https://en.wikipedia.org/wiki/Switch#Contact_bounce
  """

  use GenServer

  @debounce_time 40

  defstruct receiver: nil, last_message: nil
  @type t :: %__MODULE__{receiver: pid, last_message: any()}

  @doc """
  Pass in the pid that receives events
  """
  @spec start_link(pid()) :: :ignore | {:error, any()} | {:ok, pid()}
  def start_link(events_receiver) do
    GenServer.start_link(__MODULE__, events_receiver)
  end

  def init(receiver) do
    {:ok, %__MODULE__{receiver: receiver}}
  end

  def handle_info(:timeout, s) do
    %{receiver: receiver, last_message: message} = s
    send(receiver, message)
    {:noreply, s}
  end

  def handle_info(message, s) do
    {:noreply, %{s | last_message: message}, @debounce_time}
  end
end

The third element in the default handle_info/2 return tuple is a 40ms timeout. This means that if no more messages are received within 40ms, a :timeout message is sent to the GenServer. Only on receiving a timeout is the last message sent on to the receiving process. That is, a “key down” or “key up” message is only passed on once the circuit has settled down.

(Later on, it got a bit more sophisticated: the last message is passed on, but with the timestamp from the first message of the sequence.)