Nervesを使ってタクトスイッチを押したらLEDを光らせる(Elixir)


はじめに

作品

TORIFUKUKaiou/hello_nerves
- Nervesを使ってRaspberry pi2からTwitterの投稿を行う
- こちらの記事で作り始めたごった煮プロジェクトに追加しています

ソースコード抜粋

lib/hello_nerves/observer.ex
defmodule HelloNerves.Observer do
  use GenServer

  require Logger

  @input_pin HelloNerves.Util.input_pin()

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_info({:circuits_gpio, @input_pin, _timestamp, 1}, state) do
    Logger.debug("Received rising event on pin #{@input_pin}")
    HelloNerves.Blinker.enable()
    {:noreply, state ++ [1]}
  end

  @impl true
  def handle_info({:circuits_gpio, @input_pin, _timestamp, 0}, state) do
    Logger.debug("Received falling event on pin #{@input_pin}")
    HelloNerves.Blinker.disable()
    {:noreply, state ++ [0]}
  end
end
  • タクトスイッチが押されたり、離されたりすると、handle_info/2がコールバックされます
  • タクトスイッチが押されたら、HelloNerves.Blinker.enable()をコールして、LEDを点灯させています
  • 反対に、タクトスイッチの押し込みが離れたら、HelloNerves.Blinker.disable()をコールして、LEDを消灯させています
lib/hello_nerves/set_interrupter.ex
defmodule HelloNerves.SetInterrupter do
  use GenServer

  require Logger

  alias Circuits.GPIO

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  @impl true
  def init(state) do
    GPIO.set_interrupts(HelloNerves.Util.input_gpio(), :both, receiver: HelloNerves.Observer)
    {:ok, state}
  end
end
  • Circuits.GPIO.set_interrupts/3は、:receiveオプションでHelloNerves.Observerを指定することで、GPIOの状態変化(スイッチON/OFF)があったときに、HelloNerves.Observer.handle_info/2が呼び出されるようにしています
    • HelloNerves.Observer.init/1 で、Circuits.GPIO.set_interrupts/3を呼び出しておけばよさそうで、はじめはそうしていたのですが、数回のスイッチON/OFFにしか反応しないので、別のモジュールをつくりました
    • こうすると私のRaspberry Pi 2ではうまくいっています
    • Nerves起動後に立ち上がるIExで、GPIO.set_interrupts(HelloNerves.Util.input_gpio(), :both, receiver: HelloNerves.Observer) するとうまくいったので、こうしてみました
    • 真因がわかりましたら更新します
lib/hello_nerves/application.ex
  def children(_target) do
    [
      # Children for all targets except host
      # Starts a worker by calling: HelloNerves.Worker.start_link(arg)
      # {HelloNerves.Worker, arg},
      {HelloNerves.Blinker, name: HelloNerves.Blinker},
      {HelloNerves.Observer, name: HelloNerves.Observer},
      {HelloNerves.SetInterrupter, name: HelloNerves.SetInterrupter}
    ]
  end
  • List all child processes to be supervisedしています
    • supervisedされる子プロセスを列挙しています
    • 雑に言うと、hello_nervesというアプリケーション with Nerves が起動するときに、自動的に子プロセスが開始される感じです
  • HelloNerves.Blinkerはここには載せていません

回路

説明というか感想というか

  • nerves_examples/hello_gpio/lib/hello_gpio.ex は再帰を使った無限ループで書かれています
  • Circuits.GPIO.set_interrupts(input_gpio, :both) を呼び出して、receiveでmessageがくるのを待っています
    • Processesもあわせて読んでみると、なんとなくわかるとおもいます
  • こういう構造って、GenServerでできるということを、NervesJPで聞いたので使ってみました
  • 自分が実現したいことがあったので、プログラミングElixir(本)に書いてある内容がすんなり入ってきました