はじめに
ちょっと前の記事 はじめてね AtomVM(5) GenServer が AtomVM でもいごいた でアルファ版の AtomVM で GenServer がいごくことを確認しました。当然次は Supervisor です。
GenServer を用いたLチカ
はじめてね AtomVM(5) GenServer が AtomVM でもいごいた を元にちょっと複雑なLチカを作成しました。
- ただの単純な点滅だと他のプログラムが動作した場合と区別がつきにくいので複雑な点滅を可能にした
-
start/0関数は削除した- ブートに起動するのは後で述べる Supervisor である
- Supervisor による自動再起動を確認するのに、起動後一定時間経つとクラッシュするようにした
- GenServerがクラッシュする場合(クラッシュと言っても
stop関数) - システム全体のクラッシュする場合
- GenServerがクラッシュする場合(クラッシュと言っても
defmodule Blinky do
@compile {:no_warn_undefined, [GPIO, :atomvm]}
# for GPIO of ESP32
@gpio_pin 2
@steps 8
use GenServer
def start_link(_arg \\ []) do
IO.puts("LED Blink starts")
GenServer.start_link(__MODULE__, {@gpio_pin, 0}, name: __MODULE__)
end
def init({pin, n}) do
GPIO.set_pin_mode(pin, :output)
GPIO.digital_write(pin, :low)
set_next_step(1500)
set_crash(10_000)
IO.puts("GenServer init finished")
{:ok, {pin, n}}
end
def set_next_step(ms), do: Process.send_after(self(), :step, ms)
def set_crash(ms), do: Process.send_after(self(), :crash, ms)
def handle_info(:crash, state) do
IO.puts("crash now")
GenServer.stop(self(), :crash)
{:noreply, state}
end
def handle_info(:step, {pin, n}) do
case n do
0 -> {:noreply, {pin, {:high, 50}, n}, {:continue, :toggle_common}}
1 -> {:noreply, {pin, {:low, 50}, n}, {:continue, :toggle_common}}
2 -> {:noreply, {pin, {:high, 50}, n}, {:continue, :toggle_common}}
3 -> {:noreply, {pin, {:low, 350}, n}, {:continue, :toggle_common}}
4 -> {:noreply, {pin, {:high, 100}, n}, {:continue, :toggle_common}}
5 -> {:noreply, {pin, {:low, 100}, n}, {:continue, :toggle_common}}
6 -> {:noreply, {pin, {:high, 100}, n}, {:continue, :toggle_common}}
7 -> {:noreply, {pin, {:low, 700}, n}, {:continue, :toggle_common}}
_ -> {:noreply, {pin, {:low, 1000}, 0}, {:continue, :toggle_common}}
end
end
def handle_continue(:toggle_common, {pin, {level, interval}, n}) do
GPIO.digital_write(pin, level)
IO.puts("step #{n}, led level: #{level}, interval: #{interval} ms")
set_next_step(interval)
{:noreply, {pin, rem(n + 1, @steps)}}
end
end
これ、クラッシュさせないで動作させるとLEDが「パパ、パッパッ……」というパターンを繰り返します。
Supervisor から呼ぶようにする
上の Blinky モジュールの GenServer を起動・再起動する Supervisor を作ります。AtomVMの起動でこちらを呼ぶように start/0 関数として定義します。
defmodule AtomvmSVblinky do
def start() do
children = [Blinky]
{:ok, _pid} =
Supervisor.start_link(
children,
strategy: :one_for_one,
name: :blinky
)
Process.sleep(:infinity)
end
end
実行
クラッシュの種類を2種類用意したので、それぞれで実行してみます。
GenServer.stop によるクラッシュ
先に紳士的なクラッシュの方を試します。blinky.ex で意図的にクラッシュさせる部分は以下のようにしてあります。
GenServer.stop(self(), :crash)
# raise "crash"
依存関係解決して、コンパイルして、パックして、実機にロードします。
% mix deps.get
% mix compile
% mix atomvm.packbeam # Supervisor は無いかもよとか warning が出ます
% mix atomvm.esp32.flash --port /dev/tty.usbserial-xxx
# /dev/tty.usbserial-xxx のデバイスは各自の母艦ごとに異なります。
これでちょっと複雑なLチカが始まります。そして10秒ごとにパターンの最初からやりなおします。これは10秒ごとにGenServerがクラッシュして、それを検出したSupervisorがGenServerを立上げ直すのためにこのような動作になります。Supervisorはちゃんと機能しているようです。
これを確認するのに screen コマンドでログを見てみます。以下、色々と出てくるので動作が始まるまでの行は適当に端折ってます。
% screen /dev/tty.usbserial-xxx 115200
###########################################################
### ######## ####### ## ## ## ## ## ##
## ## ## ## ## ### ### ## ## ### ###
## ## ## ## ## #### #### ## ## #### ####
## ## ## ## ## ## ### ## ## ## ## ### ##
######### ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ##
## ## ## ####### ## ## ### ## ##
###########################################################
I (781) AtomVM: Starting AtomVM revision 0.7.0-alpha.1+git.078004b
I (791) sys: Loaded BEAM partition boot.avm at address 0x1d0000 (size=524288 bytes)
I (821) network_driver: Initialized network interface
I (821) network_driver: Created default event loop
I (841) AtomVM: Found startup beam esp32init.beam
I (851) AtomVM: Starting esp32init.beam...
---
AtomVM init.
I (931) sys: Loaded BEAM partition main.avm at address 0x250000 (size=1048576 bytes)
Starting application...
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 50 ms
step 1, led level: low, interval: 50 ms
step 2, led level: high, interval: 50 ms
step 3, led level: low, interval: 700 ms
step 4, led level: high, interval: 100 ms
step 5, led level: low, interval: 100 ms
step 6, led level: high, interval: 100 ms
step 7, led level: low, interval: 1700 ms
step 0, led level: high, interval: 50 ms
step 1, led level: low, interval: 50 ms
step 2, led level: high, interval: 50 ms
step 3, led level: low, interval: 700 ms
step 4, led level: high, interval: 100 ms
step 5, led level: low, interval: 100 ms
step 6, led level: high, interval: 100 ms
step 7, led level: low, interval: 1700 ms
とLEDを制御している様子が見えてます。そして10秒したらこうなります。ちょっと長いですが丸ごと掲載します。
step 7, led level: low, interval: 1700 ms
step 0, led level: high, interval: 50 ms
crash now
CRASH
======
pid: <0.610.0>
Stacktrace:
[{Elixir.GenServer,stop,3,[{file,"/__w/AtomVM/AtomVM/libs/exavmlib/lib/GenServer.ex"},{line,1104}]},{Elixir.Blinky,handle_info,2,[]},{gen_server,handle_msg,4,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/gen_server.erl"},{line,561}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]}]
cp: #CP<module: 5, label: 93, offset: 0>
x[0]: exit
x[1]: {calling_self,{Elixir.GenServer,stop,[<0.610.0>,crash,infinity]}}
x[2]: {0,0,0,0,[{Elixir.GenServer,stop,3,[{file,"/__w/AtomVM/AtomVM/libs/exavmlib/lib/GenServer.ex"},{line,1104}]},{Elixir.Blinky,handle_info,2,[]},{gen_server,handle_msg,4,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/gen_server.erl"},{line,561}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]}],exit}
Stack
-----
Mailbox
-------
step
Monitors
--------
link to <0.4.0>
**End Of Crash Report**
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 50 ms
step 1, led level: low, interval: 50 ms
step 2, led level: high, interval: 50 ms
step 3, led level: low, interval: 700 ms
このように GenServer が再起動します。これは Supervisor が GenServer を監視してて、GenServerが停止したことを受けて再立ち上げをしたものです。
raise/1 によるクラッシュ
先程の GenServer のプログラムでクラッシュさせるところはこうなってました。
GenServer.stop(self(), :crash)
# raise "crash"
これをraise/1 に変更します。
# GenServer.stop(self(), :crash)
raise "crash"
こうするとカーネルを強制的にクラッシュさせるようになります。この場合の結果はこのようになりました。ちょっと面白いことになってます。
step 7, led level: low, interval: 1700 ms
crash now
CRASH
======
pid: <0.5.0>
Stacktrace:
[{erlang,error,3,[]},{gen_server,handle_msg,4,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/gen_server.erl"},{line,561}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]},{proc_lib,init_p,5,[{file,"/__w/AtomVM/AtomVM/libs/estdlib/src/proc_lib.erl"},{line,272}]}]
cp: #CP<module: 5, label: 93, offset: 0>
x[0]: exit
x[1]: {#{__exc D/IRAM
I (709) heap_init: At 4009A860 len 000057A0 (21 KiB): IRAM
I (715) spi_flash: detected chip: generic
I (718) spi_flash: flash io: dio
W (721) i2c: This driver is an old driver, please migrate your application code to adapt `driver/i2c_master.h`
I (731) main_task: Started on CPU0
I (741) main_task: Calling app_main()
###########################################################
### ######## ####### ## ## ## ## ## ##
## ## ## ## ## ### ### ## ## ### ###
## ## ## ## ## #### #### ## ## #### ####
## ## ## ## ## ## ### ## ## ## ## ### ##
######### ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ##
## ## ## ####### ## ## ### ## ##
###########################################################
I (781) AtomVM: Starting AtomVM revision 0.7.0-alpha.1+git.078004b
I (791) sys: Loaded BEAM partition boot.avm at address 0x1d0000 (size=5step 5, led level: low, interval: 100 ms
step 6, led level: high, interval: 100 ms
step 7, led level: low, interval: 1700 ms
step 0, led level: high, interval: 50 ms
step 1, led level: low, interval: 50 ms
step 2, led level: high, interval: 50 ms
step 3, led level: low, interval: 700 ms
step 4, led level: high, interval: 100 ms
step 5, led level: low, interval: 100 ms
step 6, led level: high, interval: 100 ms
step 7, led level: low, interval: 1700 ms
こちら AtomVM ごと再起動してるのにも関わらず、いきなり step 5 からはじまってます。これはどうしたことでしょう。もし、AtomVM ごとESP32が再起動したのなら start/0 関数が呼ばれて Supervisor が立ち上がって、それから GenServer が立ち上がるから 0 からスタートしそうなものです。こちらは時間のあるときにもうちょっと深堀りしてみたいと思います。
まとめ
AtomVM v0.7.0-a.1 で GenServer だけでなく Supervisor が使えるのを確認しました。
リリースノートのv0.7.0-alpha.1に
- Added
code:get_object_code/1
なんて書いてあるのを見つけましたので、次回はモジュールのBEAM形式のバイナリを取り出して、それを再ロードして実行とかしてみたいと思います。ただ、そのバイナリを単体の ESP32 で code:load_binary/3 しても同じモジュールが同じAtomVMにロードされるだけです。何が起こったのかをどうやって確認すればよいのでしょうか… などという不安もあるのですが、それはまた今度。