Introduction
When we first learn Nerves, one of the things we come up with is blinking LEDs on a breadboard.
Today I'd like to try one implementation using GenServer.
This is actually my outcome at this event I participated in last weekend "Shop for parts in Akihabara and get started on Nerves right away!". It was so much fun hanging out in Akihabara, Tokyo with Nerves enthusiasts.
Thank you very much to the organizers myasu and nako_sleep_9h!
myasu is very knowledgeable about electric and electronic stuff. I learned tons of pro tips from him, which I might write up as my future posts. He is the author of this book about Nerves.
nako_sleep_9h is awesome at organizing and mc'ing fun-driven engineering events across Japan. She wrote a report on Day 1 for this event.
めでたい、光った光った〜#piyopiyoex pic.twitter.com/5WBqnFftQU
— nako@9時間睡眠 (@nako_sleep_9h) June 22, 2024
Nerves Livebook
There are more than a few ways to get started with Nerves IoT projects but one of the easiest is to use prebuild Nerves Livebook firmware. Nerves Livebook allows us to control our target devices such as Raspberry Pi, doing Elixir coding in a web browser.
https://github.com/nerves-livebook/nerves_livebook/blob/main/README.md
Blink using Circuits GPIO
Nerves Livebook has a nice tutorial notebook about how to use the circuits_gpio package to control a GPIO as an output, and blink an LED. It shows us what electric parts we need minimally and how we wire them before getting down to business.
https://github.com/nerves-livebook/nerves_livebook/blob/main/priv/samples/basics/blink.livemd
Wrapping our LED blinking logic in GenServer
When we want to repeat the blinking forever but want to stop at some stage without restarting the device, it is nice to prepare a GenServer that manages the LED blinking. There can be many ways to achieve similar effect but here is one implementation using GenServer.
First, let's write a simple module that wraps GPIO-related logic within so that we can forget about how exactly we need to communicate with our LEDs.
defmodule LedBlink do
def open(led_pin) do
Circuits.GPIO.open(led_pin, :output)
end
def close(led_pin) do
Circuits.GPIO.close(led_pin)
end
def toggle(gpio, 1), do: off(gpio)
def toggle(gpio, 0), do: on(gpio)
def toggle(gpio, _), do: on(gpio)
def on(gpio) do
:ok = Circuits.GPIO.write(gpio, 1)
{:ok, 1}
end
def off(gpio) do
:ok = Circuits.GPIO.write(gpio, 0)
{:ok, 0}
end
end
Then next, we make a GenServer that lets an LED blink forever.
defmodule BlinkServer do
use GenServer, restart: :temporary
require Logger
@run_interval_ms 1000
## Client
@doc """
Start a BlinkServer for the provided GPIO pin. It lets the LED blink forever.
### Examples
iex> BlinkServer.start_link(led_pin: "GPIO17")
"""
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def stop() do
GenServer.stop(__MODULE__)
end
## Callbacks
@impl true
def init(opts) do
led_pin = Access.fetch!(opts, :led_pin)
initial_state = %{
gpio_ref: nil,
led_state: 0,
led_pin: led_pin
}
{:ok, initial_state, {:continue, :init_gpio}}
end
@impl true
def handle_continue(:init_gpio, state) do
case LedBlink.open(state.led_pin) do
{:ok, gpio_ref} ->
new_state = %{state | gpio_ref: gpio_ref}
send(self(), :toggle_led_state)
{:noreply, new_state}
{:error, error} ->
{:stop, error}
end
end
@impl true
def handle_info(:toggle_led_state, state) do
{:ok, new_led_state} = LedBlink.toggle(state.gpio_ref, state.led_state)
new_state = %{state | led_state: new_led_state}
Logger.debug("toggled LED: #{new_state.led_state}")
Process.send_after(self(), :toggle_led_state, @run_interval_ms)
{:noreply, new_state}
end
@impl true
def terminate(reason, state) do
LedBlink.close(state.gpio_ref)
Logger.debug("terminated: #{reason}")
:ok
end
end
Here is how to use our BlinkServer.
When starting BlinkServer, we specify the GPIO pin we use for our LED.
When we are done experimenting, we can stop our running BlinkServer worker.
Logger.configure(level: :debug)
BlinkServer.start_link(led_pin: "GPIO17")
Process.sleep(10_000)
BlinkServer.stop()
This implementation of BlinkServer is a singleton GenServer, meaning we are not allowed to start more than one BlinkServer workers. So if we change our mind on the GPIO pin we use, for example, we would need to stop the worker and start a new worker with different options.
circuits_gpio package
circuits_gpio is the one that lets us control GPIO in our Elixir code.
Wrapping up
We wrote a simple GenServer that lets our LED blink.
Once you get a feel of it, I would like to see your LED blinking logic. Please share!
今日は初Nerves!
— FRICK (@TewiEwi_no96) June 21, 2024
8月末のSWESTに向けた知識を付けていこう~ https://t.co/SrGEUJjzkS
Nerves勉強会2日目!
— FRICK (@TewiEwi_no96) June 23, 2024
開始〜 https://t.co/zgMKDEGrH8 pic.twitter.com/rT7QiHslAa
#piyopiyoex
— ysaito (@ysaito8015) June 23, 2024
【オフライン】秋葉原でパーツお買い物&そのままNerves入門!
はじまたー pic.twitter.com/BPiPBxZMTq
神田明神きたよ#piyopiyoex pic.twitter.com/xuxuiWcl2F
— nako@9時間睡眠 (@nako_sleep_9h) June 22, 2024
今日はこちらへ参加! https://t.co/L28N3O6nIF
— myasu (@etcinitd) June 22, 2024
今日はDay2!!https://t.co/hgJwwqGO9W
— myasu (@etcinitd) June 23, 2024
Top comments (0)