1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

前の記事 はじめてね 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/"} を追加
    • projectatomvm: [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 loadingThe 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マシンだとありがたみがないので、今度は違うマシンでモジュールを渡してみたいです。

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?