はじめに
前の記事 はじめてね AtomVM(7) Supervisor が AtomVM でもいごいた の最後に
リリースノートのv0.7.0-alpha.1に
- Added
code:get_object_code/1なんて書いてあるのを見つけました
とあります。Elixir は、いごいてるBEAMマシン上のモジュールを取出して、それを別のBEAMマシンにロードして実行するという離れ業が可能です。今回はお手軽に同じマシンで試して使えることを確認してみます。
詳しくは AtomVM 公式ドキュメント code loading も見てください。
いざハック
これまでの例に従ってコード書いてデプロイします。
準備
既にいごいてる元のディレクトリから tar コマンドでディレクトリをごっそり持ってきて新しいプロジェクト作ったら、色々と細かくハマったので、ちゃんと mix new から作りましょう。
-
mix new atomvm_codeload --module AtoVMcodeloadでプロジェクト作成 -
cd atomvm_codeloadそこのディレクトリに移動 -
mix.exsの修正-
depsに{:exatomvm, git: "https://github.com/atomvm/ExAtomVM/"}を追加 -
projectにatomvm: [start: AtomVMcodeload, flash_offset: 0x250000]を追加(ESP32の場合)
-
-
mix deps.getを実行 - 必要なら
avm_depsディレクトリを作成して、AtomVM のファームウェアを github リポジトリからゲット
取出されて再配置されるモジュールを作成
LEDの点灯パターンをリストで書けるようにしました。
defmodule Blinky do
@compile {:no_warn_undefined, [GPIO, :atomvm]}
# for GPIO of ESP32
@gpio_pin 2
@default_pattern [
{:high, 200},
{:low, 800}
]
use GenServer
def start_link(pattern \\ @default_pattern)
def start_link([]), do: start_link(@default_pattern)
def start_link(pattern) do
IO.puts("LED Blink starts")
GenServer.start_link(__MODULE__, {@gpio_pin, pattern, 0}, name: __MODULE__)
end
def stop(pid) do
IO.puts("LED Blink stops")
GenServer.stop(pid)
end
@impl true
def init({pin, pattern, n}) do
GPIO.set_pin_mode(pin, :output)
GPIO.digital_write(pin, :low)
set_next_step(1500)
IO.puts("GenServer init finished")
{:ok, {pin, pattern, n}}
end
def set_next_step(ms), do: Process.send_after(self(), :step, ms)
@impl true
def handle_info(:step, {pin, pattern, n}) do
{level, interval} = Enum.at(pattern, n)
{:noreply, {pin, pattern, {level, interval}, n}, {:continue, :toggle_common}}
end
@impl true
def handle_continue(:toggle_common, {pin, pattern, {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, pattern, rem(n + 1, length(pattern))}}
end
end
モジュールを取出して再配置するプログラム
モジュールのBEAM VMコードを取り出す関数が :code.get_object_code/1 です。これにモジュール名を与えると「モジュール名・モジュールの実行コード・実行コードを定義したファイルのファイル名」のタプルが帰ります。
これを別のノードで :code.load_binary/3 にくべてやると、そのノードでモジュールが生えます。今回は単一のマシンでやるので、既にあるモジュールと同じモジュールを搭載するだけですので、あまりおもしろくはありません。
重要な注:これらはペアで使いますが :code.get_object_code/1 の返す値の順序「モジュール名・モジュールの実行コード・実行コードを定義したファイルのファイル名」と、code:load_binary/3 に与える引数の順序「モジュール名・ファイル名・実行コード」が違うことに注意です。なんでこんなことになってるのかちょっとわかりません。
以下のプログラムは10秒ごとに Blinky モジュールを取出しては再度配置して、それを交互に異なるパターンの引数で実行させ(GenServerにチカらせ)ます。
defmodule AtomVMcodeload do
def start() do
IO.puts("AtomVM codeload starts")
test_loop(0)
end
def test_loop(n) do
IO.puts("AtomVM codeload test loop #{n}")
{:ok, pid} = Blinky.start_link(pattern(n))
Process.sleep(10_000)
{Blinky, binary, filename} = :code.get_object_code(Blinky)
:code.load_binary(Blinky, filename, binary)
Blinky.stop(pid)
test_loop(n + 1)
end
def pattern(n) do
case rem(n, 2) do
0 ->
[{:high, 100}, {:low, 100}, {:high, 300}, {:low, 500}]
1 ->
[
{:high, 300},
{:low, 100},
{:high, 100},
{:low, 100},
{:high, 100},
{:low, 100},
{:high, 100},
{:low, 500}
]
_ ->
[{:high, 200}, {:low, 800}]
end
end
end
なお、ここでのファイル名は AtomVM のcode:load_binary/3では無視されるようです。AtomVM 公式ドキュメント code loadingに The Filename parameter is currently ignored. とあります。
さらに AtomVM を(マイコンではなく)Unix マシンで使う場合は code:load_abs/1 に beam モジュールのファイル名を(.beamの拡張子ナシで)与えることでモジュールをロードできるようなことが記載してあります。
実行する
Lチカはモールス符号の 'A' と 'B' を交互に表示します。
ターミナルへの出力をscreenコマンドで見るとこんなふうになります。
---
AtomVM init.
I (931) sys: Loaded BEAM partition main.avm at address 0x250000 (size=1048576 bytes)
Starting application...
AtomVM codeload starts
AtomVM codeload test loop 0
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 100 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 300 ms
step 3, led level: low, interval: 500 ms
...snip...
step 0, led level: high, interval: 100 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 300 ms
step 3, led level: low, interval: 500 ms
LED Blink stops
AtomVM codeload test loop 1
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 300 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 100 ms
step 3, led level: low, interval: 100 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: 500 ms
...snip...
step 0, led level: high, interval: 300 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 100 ms
step 3, led level: low, interval: 100 ms
step 4, led level: high, interval: 100 ms
LED Blink stops
AtomVM codeload test loop 2
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 100 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 300 ms
step 3, led level: low, interval: 500 ms
...snip...
step 0, led level: high, interval: 100 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 300 ms
step 3, led level: low, interval: 500 ms
LED Blink stops
AtomVM codeload test loop 3
LED Blink starts
GenServer init finished
step 0, led level: high, interval: 300 ms
step 1, led level: low, interval: 100 ms
step 2, led level: high, interval: 100 ms
step 3, led level: low, interval: 100 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: 500 ms
...snip...
まとめ
AtomVM v0.7.0-a.1 で :coce.get_object_code/1 と :code.load_binary/3 が使えるのを確認しました。
同じBEAMマシンだとありがたみがないので、今度は違うマシンでモジュールを渡してみたいです。