この記事は、#NervesJP Advent Calendar 2020 12日目です。
前日は、 @mnishiguchi さんの「Elixir/Nervesでパルス幅変調 (PWM) Lチカ」でした。
こちらもお楽しみください。
はじめに
-
@kentaroさんの kentaro/mix_tasks_upload_hotswapを試してみます
- この記事で試したリビジョン 5d0fc4ff0c0a4a8d66ad13b032391c393fdd3a05
- あ、そうそうHappy birthday!!!
- Nerves界に衝撃が走りました
- 試供品付き(
/example
)なのでそれをそのまま動かします - Elixr:
1.11.2-otp-23
-
Nervesアプリを作れる環境が必要です
- @takasehideki 先生のElixirでIoT#4.1:Nerves開発環境の準備
- @kentaroさんの ウェブチカでElixir/Nervesに入門する(2020年12月版)
- これらをご参照ください
では、さっそくためしていきます
$ git clone https://github.com/kentaro/mix_tasks_upload_hotswap.git
$ cd mix_tasks_upload_hotswap/example
$ export MIX_TARGET=rpi4
$ export MY_NETWORK_SSID=your_ssid
$ export MY_NETWORK_PSK=your_psk
$ mix deps.get
$ mix firmware
-
MIX_TARGET
はお使いのハードウェアにあわせてください- 他に使用できるものは、Targetsをご参照ください
-
MY_NETWORK_SSID
とMY_NETWORK_PSK
はWi-FiでRaspberry Piを接続する場合に指定してください- 2.4GHz(G)と5GHz(A)が使える場合は、2.4GHz(G)の方を使っておいたほうが無難という噂があります
- LANケーブルでネットワークに接続する場合は必要ありません
- Firmwareがビルドできたら、開発用マシンにmicroSDカードを挿して
$ mix burn
- このコマンドで焼き込みます
- こんがり焼き上がったmicroSDカードをRaspberry Pi 4にさして電源ON !!!
シナリオ1
REDAMEに書いてある通り、Example.hello/0
のAtomをかえてみる(:world
-> :"new world")
$ ssh nerves.local
iex(example@nerves.local)> Example.hello
:world
-
Example.hello/0
の結果は:world
であることを確認したらソースコードを書き換えます
example/lib/example.ex
--- a/example/lib/example.ex
+++ b/example/lib/example.ex
@@ -13,7 +13,7 @@ defmodule Example do
"""
def hello do
- :world
+ :"new world"
end
- ふつうはここで
mix firmware && mix upload
とやるわけですが、今回はそうではありません - 開発マシンから以下のコマンドを打ち込みます
$ mix upload.hotswap
-
mix firmware && mix upload
をやるのと比較するとほぼ**一瞬**でおわります - もう一度、
Example.hello/0
の結果を確認してみます
iex(example@nerves.local)> Example.hello
:"new world"
:world
->:"new world"
に変わっている!- まじNervesアプリ書き換わっているよ! → 成功
シナリオ2
Awesomeモジュール追加(Awesome.hello/0)
- これはさすがに対応していないんじゃないかな?
example/lib/awesome.ex
defmodule Awesome do
def hello do
"NervesはElixirのIoTでナウでヤングなcoolなすごいヤツです🚀 (https://twitter.com/torifukukaiou/status/1201266889990623233)"
end
end
- モジュールを追加したらおもむろに、開発マシンから
$ mix upload.hotswap
Successfully connected to example@nerves.local
Successfully deployed Elixir.Awesome to example@nerves.local
Successfully deployed Elixir.Example to example@nerves.local
Successfully deployed Elixir.Example.Application to example@nerves.local
Successfully deployed Elixir.Example.Counter to example@nerves.local
Elixir.Awesome
がdeployされた!?- Nervesで動作を確認してみましょう
iex(example@nerves.local)> Awesome.hello
"NervesはElixirのIoTでナウでヤングなcoolなすごいヤツです🚀 (https://twitter.com/torifukukaiou/status/1201266889990623233)"
- これすげーよ、対応しちょるよ!
このHexどんな複雑なことをやっているのだろう?
- えっ、
lib/mix/tasks/mix/tasks/upload/hotswap.ex
1ファイルしかない - Mix.Tasks.Upload.Hotswap.run 関数もそれほど長くはない
- ふむふむ Node.connect/1して、 (@kikuyuta 先生の記事を読めば理解できる(とおもう)!)
-
modules
をひっぱってきて IEx.Helpers.nl/2 を呼ぶのだな
シナリオ3
- 今度こそこれはどうだろう?
Timexをmix.exs
に追加して、Timex.now/0を呼び出してみる
example/mix.exs
--- a/example/mix.exs
+++ b/example/mix.exs
@@ -52,7 +52,8 @@ defmodule Example.MixProject do
{:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64},
# Local dependencies
- {:mix_tasks_upload_hotswap, path: "../", only: :dev}
+ {:mix_tasks_upload_hotswap, path: "../", only: :dev},
+ {:timex, "~> 3.5"}
]
end
example/lib/example.ex
@@ -19,4 +19,8 @@ defmodule Example do
def increment do
GenServer.call(Example.Counter, :increment)
end
+
+ def now do
+ Timex.now()
+ end
end
- この状態で、
mix upload.hotswap
してもTimex
はNervesアプリに入りません- この時点で
mix upload.hotswap
を実行してしまうと以下の変更をしても反映されないのでまだ待ってください - Timexなぞ知らぬといわれる のです
- これはさすがにだめか
- けれど**歌うアルケミストと呼ばれる、自称アルケミストのはしくれ、名乗るほどのものでもないが、解決しろと血がさわぐ**
- この時点で
- 以下の変更をすると、(本当に)とりあえずイゴきました ザマス
lib/mix/tasks/mix/tasks/upload/hotswap.ex
- {:ok, modules} = :application.get_key(app_name, :modules)
+ # {:ok, modules} = :application.get_key(app_name, :modules)
+ IO.puts :awesome
+ modules = :code.all_loaded() |> Enum.map(&elem(&1, 0))
for module <- modules do
for node <- nodes do
@@ -57,6 +59,10 @@ defmodule Mix.Tasks.Upload.Hotswap do
IO.puts("Successfully deployed #{module} to #{node}")
end
defp handle_load_module({:ok, [{_, :loaded, _}]}, module, node) do
IO.puts("Successfully deployed #{module} to #{node}")
end
+ defp handle_load_module({:ok, [{_, hoge, fuga}]}, module, node) do
+ IO.puts("#{hoge} #{fuga} #{module} to #{node}")
+ end
-
:application.get_key(app_name, :modules)
だと自分が追加したモジュール情報しか取れないようなので全部とっちゃえばいいのじゃなかろうか(そんな発想です) -
:code.all_loaded
なんて、なんのことだかさっぱりわからないけれど、天が教えてくれた(たぶん ↓↓↓)
$ mix upload.hotswap
- すごくだーっと流れます
- ヒヤヒヤします
- スピード・スリル・サスペンス 飯塚オートです
- 変数名、とりあえず
hoge
とかfuga
です - 一応、(本当に一応)これ動きました!!!
- mix firmware && mix burn よりは断然速いです!(体感)
iex(example@nerves.local)2> Example.now
~U[2020-12-09 12:55:24.790199Z]
ただし
- Timexは上の手順で入りましたが、HTTPoisonはうまく動きませんでした(依存ライブラリが完全に入り切らず足りない)
- シナリオ3の私のホットフィックスはいろいろあやしいです
- 気づいたことを披露しておきます
- 上記の通りを一気にやると
Timex.now/0
を呼び出せることは確認したのですが、なにか変更をさらに加えてmix upload.hotswap
したときのNervesアプリの動作がさらにあやしさ満点です-
ssh nerves.local
の接続が切れている -
Example.now/0
を呼び出したときにまたふたたびTimex
なぞ知らぬと言われる
-
-
mix deps.get
して、mix upload.hotswap
をしたときにTimex
およびその依存モジュールがビルドされるのですがその最初のときだけ私のホットフィックスでTimex
がNervesアプリに入るようです
iex(example@nerves.local)2> Example.now
** (UndefinedFunctionError) function Timex.now/0 is undefined (module Timex is not available)
Timex.now()
シナリオ4
- パソコン側で
iex -S mix
$ iex -S mix
Erlang/OTP 23 [erts-11.0.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
iex> :application.get_key(:example, :modules)
{:ok, [Example, Example.Application, Example.Counter]}
Wrapping Up
- kentaro/mix_tasks_upload_hotswapは、とにかくすごい!
-
mix deps.get
するようなときは一回mix firmware && mix upload
をしてmicroSDカードを落ち着けてから、自分の追加モジュールをイジイジしてmix upload.hotswap
で効率よく開発して、コードが固まったらまたそこでmix firmware && mix upload
するというような使い方でだいぶ開発効率はあがるようにおもいます - さっそく私のごった煮プロジェクトTORIFUKUKaiou/hello_nervesには追加していきたいとおもいます
- Enjoy Elixir !!!
明日は@kentaro さんご本人がkentaro/mix_tasks_upload_hotswapを語ってくださいます。「mix upload.hotswap
(kentaro/mix_tasks_upload_hotswap)の裏側」もぜひお楽しみください。
I can’t wait until tomorrow.