Nervesでのアプリケーション自動起動の手順(Version1.5系の場合)
以下の環境をベースにしています。
- OS : macOS Mojave
- Elixir : 1.9.0
- Nerves : 1.5.3
- Nerves Bootstrap: 1.6.2
- Nervesのターゲットマシン: Raspberry Pi Zero WH
前説
Nervesとは、プログラミング言語Elixirを使って組込みソフトウェア開発を行うためのフレームワークです。
非常に軽量なLinuxブートローダー+ErlangVM+Elixir実行環境を提供してくれる優れものでもあります。加えて、mixだけで「IoTを操作するためのNervesのプロジェクト」が簡単に生成できてしまいます。
これによって、IoTの操作をするために、ラズパイ用のOSの環境設定をしたり慣れないC言語をさわったりといった煩わしさから解放されます。
また、2019年にはNervesJPのアドベントカレンダーもできており、全て埋まっています。このように、一部では非常に注目を浴びているんです。
まったくもって、NervesはElixirのIoTでナウでヤングなcoolなすごいヤツですね!
本題
Nerves Examplesのサンプルのファイル構成と、2020年1月時点でmix nerves.new <プロジェクト名>
を実行した際のファイル構成にギャップがあったので整理してみました。
具体的な違いとしては、lib/<プロジェクト名>/application.ex
の存在です。
Nerves Examplesのプロジェクトにはlib/<プロジェクト名>/application.ex
はありません。
一方で、Nervesの1.5系で作成したNervesプロジェクトにはlib/<プロジェクト名>/application.ex
ファイルがあり、かつmix.exs
のdef application do
中のmod:
で定義されているので最初に実行されます。
やりたいこと
Nervesを組み込んだSDカードを刺したIoT機器上に対して、電源を入れたら何のトリガーも必要とせず自動的にプロセスが動くこと、です。
したがって、mix nerves.new
をした後に自動生成されているlib/<プロジェクト名>/application.ex
から、どのようにして自分で作成したモジュールや関数を呼び出すか、が課題となります。
対応方法:
通常のスーパーバイザーやGenServerの仕組みと同じように用意すれば良いです。
箇条書きにすると、次のようになります。
-
lib/<プロジェクト名>/application.ex
のchildren
の要素に、スーパーバイザーのワーカーとなるモジュールを設定します。 - 前述の1.で設定した、実行するモジュールのファイルを用意します。そのモジュール内では、以下の設定/関数を用意します。
具体例
Raspberry Pi Zero WH(以降、rpi0)に対して、一定の間隔で以下を繰り返すようなプログラムを書きます。
- 自身のLEDを点滅させる
- GPIOを操作して出力のon/offを切り替える
プロジェクト名はnerves_gpio_sample
とします。
コンソールにmix nerves.new nerves_gpio_sample
と入力し、nerves_gpio_sample
のプロジェクトを作成します。
コード修正
以下、3点のコードの修正をします。
- ライブラリとして利用する「Circuits.GPIO」3や「Nerves.Leds」4を、
mix.exs
に設定(追記)します。 -
lib/nerves_gpio_sample/application.ex
に対して、ワーカーになるモジュールNervesGpioSample.Led
を設定(追記)します。5 -
lib/nerves_gpio_sample/led_raspi.ex
を新規作成します。その中で、GenServerの振る舞いとワーカー対応用のコールバック関数、およびLチカ用に処理を記述します。6
defp deps do
[
# Dependencies for all targets
{:nerves, "~> 1.5.0", runtime: false},
{:shoehorn, "~> 0.6"},
〜(中略)〜
#############
## 最後に、以下の2つを追加
#############
{:nerves_leds, "~> 0.8", targets: @all_targets},
{:circuits_gpio, "~> 0.4", targets: @all_targets},
]
end
def children(_target) do
[
# Children for all targets except host
# Starts a worker by calling: NervesGpioSample.Worker.start_link(arg)
# {NervesGpioSample.Worker, arg},
{NervesGpioSample.Led, []}, # <- ここにワーカーになるモジュール名を追加。
]
end
defmodule NervesGpioSample.Led do
use GenServer
## 短縮名で利用できるように宣言
alias Nerves.Leds
alias Circuits.GPIO
## スーパーバイザー側からcallされる処理。
## 関数内で、GenServer.start_link/3を呼び出し。
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
## GenServer.start_linkによって呼び出される初期化処理。
def init(state) do
run()
{:ok, state}
end
### これ以降は、Lチカ/GPIO操作用の関数
def run() do
led_list = Application.get_env(:nerves_gpio_sample, :led_list)
spawn(fn -> blink_list_forever(led_list) end)
end
defp blink_list_forever(led_list) do
Enum.each(led_list, &blink(&1))
blink_list_forever(led_list)
end
defp blink(led_key) do
1..10 |> Enum.each(fn _ -> led_set(led_key,{300, 300}) end)
{:ok, gpio17} = GPIO.open(17, :output)
{:ok, gpio27} = GPIO.open(27, :output)
GPIO.write(gpio17, 1)
led_set(led_key,{3000, 2000})
GPIO.write(gpio17, 0)
GPIO.write(gpio27, 1)
led_set(led_key,{2000, 3000})
GPIO.write(gpio27, 0)
end
defp led_set(led_key, {on_time, off_time}) do
Leds.set([{led_key, true}])
:timer.sleep(on_time)
Leds.set([{led_key, false}])
:timer.sleep(off_time)
end
end
実施結果:
電源を入れると、しばらくしてLチカ+GPIOによる出力で外部回路のLEDを点灯/消灯を確認できました。
以下の動きを繰り返します。
- rpi0のLEDが点灯/消灯が、0.3秒間ずつ10回くりかえされます
- GPIO17がonになり外部回路のLEDが点灯、同時にrpi0のLEDが3秒点灯/2秒消灯します。
- GPIO17がoffになってGPIO27がon(この時、GPIOの17番と27番にそれぞれ繋いでいる外部回路のLEDも消灯/点灯)になります。同時にrpi0のLEDが2秒点灯/3秒消灯します。
- GPIO27がoffになり外部回路のLEDも消灯、最初の動作に戻ります。
参考情報
実施内容自体は、Elixir Schoolの「Nerves」のページに書いてありました。
また、こちらのQiita記事も参考にさせていただきました。
加えて、「GenServer」7や「Supervisor」8のマニュアルも参照しました。
Nervesの内容というよりも、OTPの並行性(SupervisorやGenServerなども含む)に関する情報になった気がします。
また、init/1
でspawn
させているあたり、GenServerの利点を活かせていないのでツッコミどころはあるかと思っていますが...
handle_cast/2
を用意し、init/1
の中でGenServer.cast
を実行するパターンも試してみたので、内容を整理できしだい追記して行こうと思います。
-
ここでいう"状態"とは、OTPサーバでいうところの"状態"を指します。 ↩
-
もしくは、さらにGenServerのコールバック関数(
handle_call/3
やhandle_cast/2
など)を実装し、init/1からGenServer.callやGenServer.castを呼び出してメインの処理が実行されるようにします。 ↩ -
厳密には、
start/2
関数の中で呼び出しているSupervisor.start_link(children, opts)
に渡している引数children
の中に設定されていれば良いです。ですので、start/2
関数の中で定義しても可です。 ↩ -
init/1関数からrun/0関数を起動し、spawnを利用してさらに別プロセスで起動。GPIOについては、GPIO17番と27番を利用して、on/offの実施。 ↩
Author And Source
この問題について(Nervesでのアプリケーション自動起動の手順(Version1.5系の場合)), 我々は、より多くの情報をここで見つけました https://qiita.com/MzRyuKa/items/1e4f93d80505e178194a著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .