はじめに
Nerves を初めて学ぶとき、ブレッドボード上で LED を点滅させたくなることがあると思います。
やり方は色々考えられますが、今日は GenServer を使用した簡単な実装に挑戦してみたいと思います。
本記事は、先週末に参加したイベントでの成果です。Nerves 愛好家と東京の秋葉原で買い物を楽しみ、Nervesを使った IoT デバイス開発について学びました。主催者のmyasuさん と nako_sleep_9hさんに心から感謝します!
myasu さんは電気電子工学についてとても詳しい方で、秋葉原を散策しながら色々教えていただきました。大変勉強になりました。また、myasu さんが執筆された Nerves 開発の入門書もあります。
nako_sleep_9h さんは、日本各地で元氣になる楽しい技術イベント(オンライン及びオフライン)を企画し、また司会も務められています。
めでたい、光った光った〜#piyopiyoex pic.twitter.com/5WBqnFftQU
— nako@9時間睡眠 (@nako_sleep_9h) June 22, 2024
Nerves Livebook
Nerves をプラットフォームとして IoT プロジェクトを始めるにはいくつかの方法がありますが、最も簡単な方法の 1 つは、ビルド済みの Nerves Livebook ファームウェアを使用することです。Nerves Livebook を使用すると、Web ブラウザー上で Elixir コーディングを行いながら、Raspberry Pi などのターゲット デバイスを制御できます。
有志の方々が Nerves Livebook のセットアップ方法ついてのビデオを制作してくださっています。ありがとうございます。
circuits_gpio を使って LED を操作
circuits_gpio は、Elixir コードで GPIO を制御できるようにするものです。
Nerves Livebook には、circuits_gpio パッケージを使用して GPIO を出力として制御し、LED を点滅させる方法についての良いチュートリアル ノートブックがあります。作業に取り掛かる前に、最低限必要な電気部品と、それらの配線方法を示しています。
LED 点滅ロジックを GenServer でラップ
点滅を永遠に繰り返したいが、デバイスを再起動せずにある段階で停止したい場合、LED の点滅を管理する GenServer を用意すると便利です。
まず、GPIO 関連のロジックをラップする簡単なモジュールを作成し、LED を操作するための関数を定義します。
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
次に、LED を永久に点滅させるGenServerを作成します。
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
論より Run
BlinkServer の使用方法は次のとおりです。
BlinkServer を起動するときに、LED の繋がっている GPIO ピンを指定します。
実行中の BlinkServer ワーカーを停止することもできます。
Logger.configure(level: :debug)
BlinkServer.start_link(led_pin: "GPIO17")
Process.sleep(10_000)
BlinkServer.stop()
今回の BlinkServer の実装はシングルトンの GenServer です。つまり、複数の BlinkServer ワーカーを起動することはできません。そのため、たとえば使用する GPIO ピンを変更する場合は、ワーカーを停止し、異なるオプションで新しいワーカーを起動する必要があります。
Wrapping up
LED を点滅させるシンプルな GenServer を作成しました。
世の中にはいろんな LED 点滅のロジックや楽しみ方があります。あなたもぜひ共有してみてください!

