ZenohexをNervesでいごかす v0.3.2篇

もう師走だなんて信じられない！11月31日はどこにいった！！
なんて言ってても時は戻らないので，雑ネタを投下したいと思います．

2023年は一本もQiita記事をアウトプットしておらず，そして #NervesJP Advent Calendar 2023 も飛ばしてしまったので，その罪滅ぼしも兼ねまして,,, 
みなさんれっつじょいなす！！

https://qiita.com/advent-calendar/2024/nervesjp

# Zenohexとは？？

ZenohのElixir APIライブラリです．
@ShintaroHosoai や @pojiro と楽しくつくってます:D

https://github.com/biyooon-ex/zenohex

いろんなご縁がありまして7月の daimon.ex にて講演させてもらいました．YouTubeアーカイブも公開してくれていて，Alchemist向け前提ではあるものの，ZenohやZenohexの解説内容としては一番まとまっているのではと思っています．

https://daimon-ex.connpass.com/event/318529/

https://speakerdeck.com/takasehideki/daimonex-20240719

<iframe width="560" height="315" src="https://www.youtube.com/embed/h6gZ2_5mI2E?si=qMSvoUvbG3WhcpnX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

このへんの記事も参考になるかと思います，というか私も参考にしています:D

https://qiita.com/Shintaro_Hosoai/items/0bde489cde43a00d6f96

https://qiita.com/myasu/items/ae53aba8ecac1cd3596c

https://qiita.com/motii8128/items/2d4335d1fc43b9fc86c5

ネイティブなElixirで試されている記事も！（Zenohexバージョンが v0.1.3 で古めです）

https://qiita.com/kikuyuta/items/8e89efb8b35aebbdf66c

という感じで Zenohex on Nerves はまだ記事がなさそうなのでセーフセーフ，ということでやっていきます〜

# 下準備

## ホスト環境

ホストは MacBook Pro 2021 / Apple M1 Max でやっていきます．
Elixir/Erlang/Nerves 関係の環境は構築済みであることを前提とします．ちなバージョンはこんな感じ．

```bash
% asdf current                  
elixir          1.17.3-otp-27   /Users/takasehideki/.tool-versions
erlang          27.1.2          /Users/takasehideki/.tool-versions
```

## Nerves mix プロジェクトの作成

いつものように Nerves mix プロジェクトを作成して初期ビルドしておきます．

```bash
% mix nerves.new zenohex_nerves           
* creating zenohex_nerves/config/config.exs
* creating zenohex_nerves/config/host.exs
* creating zenohex_nerves/config/target.exs
* creating zenohex_nerves/lib/zenohex_nerves.ex
* creating zenohex_nerves/lib/zenohex_nerves/application.ex
* creating zenohex_nerves/test/test_helper.exs
* creating zenohex_nerves/test/zenohex_nerves_test.exs
* creating zenohex_nerves/rel/vm.args.eex
* creating zenohex_nerves/rootfs_overlay/etc/iex.exs
* creating zenohex_nerves/.gitignore
* creating zenohex_nerves/.formatter.exs
* creating zenohex_nerves/mix.exs
* creating zenohex_nerves/README.md

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix format
Your Nerves project was created successfully.

You should now pick a target. See https://hexdocs.pm/nerves/supported-targets.html
for supported targets. If your target is on the list, set `MIX_TARGET`
to its tag name:

For example, for the Raspberry Pi 3 you can either
  $ export MIX_TARGET=rpi3
Or prefix `mix` commands like the following:
  $ MIX_TARGET=rpi3 mix firmware

If you will be using a custom system, update the `mix.exs`
dependencies to point to desired system's package.

Now download the dependencies and build a firmware archive:
  $ cd zenohex_nerves
  $ mix deps.get
  $ mix firmware

If your target boots up using an SDCard (like the Raspberry Pi 3),
then insert an SDCard into a reader on your computer and run:
  $ mix burn

Plug the SDCard into the target and power it up. See target documentation
above for more information and other targets.

```

## mix deps.get

これもいつものやつですね．
ターゲットは目の前に転がっていたラズパイ４にしました．

```bash
% cd zenohex_nerves
% export MIX_TARGET=rpi4
% mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.09s
Unchanged:
  beam_notify 1.1.1
  castore 1.0.10
  circular_buffer 0.4.1
  elixir_make 0.9.0
  gen_state_machine 3.0.0
  jason 1.4.4
  mdns_lite 0.8.11
  muontrap 1.5.0
  nerves 1.11.2
  nerves_logging 0.2.2
  nerves_motd 0.1.15
  nerves_pack 0.7.1
  nerves_runtime 0.13.7
  nerves_ssh 1.0.0
  nerves_system_bbb 2.25.0
  nerves_system_br 1.29.1
  nerves_system_grisp2 0.13.0
  nerves_system_mangopi_mq_pro 0.11.0
  nerves_system_osd32mp1 0.20.0
  nerves_system_rpi 1.29.0
  nerves_system_rpi0 1.29.0
  nerves_system_rpi2 1.29.0
  nerves_system_rpi3 1.29.0
  nerves_system_rpi3a 1.29.0
  nerves_system_rpi4 1.29.0
  nerves_system_rpi5 0.4.0
  nerves_system_x86_64 1.29.0
  nerves_time 0.4.8
  nerves_toolchain_aarch64_nerves_linux_gnu 13.2.0
  nerves_toolchain_armv6_nerves_linux_gnueabihf 13.2.0
  nerves_toolchain_armv7_nerves_linux_gnueabihf 13.2.0
  nerves_toolchain_ctng 1.10.0
  nerves_toolchain_riscv64_nerves_linux_gnu 13.2.0
  nerves_toolchain_x86_64_nerves_linux_musl 13.2.0
  nerves_uevent 0.1.0
  one_dhcpd 2.0.3
  property_table 0.2.5
  ring_logger 0.11.3
  shoehorn 0.9.2
  ssh_subsystem_fwup 0.6.2
  toolshed 0.4.1
  uboot_env 1.0.1
  vintage_net 0.13.5
  vintage_net_direct 0.10.7
  vintage_net_ethernet 0.11.2
  vintage_net_wifi 0.12.5
All dependencies are up to date
==> jason
Compiling 10 files (.ex)
Generated jason app
==> elixir_make
Compiling 8 files (.ex)
Generated elixir_make app
==> castore
Compiling 1 file (.ex)
Generated castore app
==> nerves
HOST_CC port.o
HOST_LD port
Compiling 51 files (.ex)
Generated nerves app
==> zenohex_nerves

Nerves environment
  MIX_TARGET:   rpi4
  MIX_ENV:      dev

Checking for prebuilt Nerves artifacts...
  Checking nerves_system_rpi4...
  [GitHub] nerves_system_rpi4-portable-1.29.0-FB67112.tar.gz
|==================================================| 100% (274 / 274) MB
  => Success
  Found nerves_toolchain_aarch64_nerves_linux_gnu in cache
    /Users/takasehideki/.nerves/artifacts/nerves_toolchain_aarch64_nerves_linux_gnu-darwin_arm-13.2.0

```

## Zenohex のインストール

いつものように `mix.exs` を編集します．

```mix.exs
  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
<snipped...>
      {:nerves_system_mangopi_mq_pro, "~> 0.6", runtime: false, targets: :mangopi_mq_pro},

      # Add zenohex hex package
      {:zenohex, "~> 0.3.2"}
    ]
  end
```

改めて `mix deps.get` しておきます．

```bash
% mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.109s
New:
  rustler_precompiled 0.8.2
  toml 0.7.0
  zenohex 0.3.2
Unchanged:
<snipped...>
* Getting zenohex (Hex package)
* Getting rustler_precompiled (Hex package)
* Getting toml (Hex package)
==> nerves
==> zenohex_nerves

Nerves environment
  MIX_TARGET:   rpi4
  MIX_ENV:      dev

Checking for prebuilt Nerves artifacts...
  Found nerves_toolchain_aarch64_nerves_linux_gnu in cache
    /Users/takasehideki/.nerves/artifacts/nerves_toolchain_aarch64_nerves_linux_gnu-darwin_arm-13.2.0
  Found nerves_system_rpi4 in cache
    /Users/takasehideki/.nerves/artifacts/nerves_system_rpi4-portable-1.29.0
```

## さてビルド

Zenoh は Rust 実装されていますが，NIF的な [Rustler](https://github.com/rusterlium/rustler) と  [Rustler Precompiled](https://github.com/philss/rustler_precompiled) のおかげでユーザはそこをあんまり気にする必要ありません．ビルド時に prebuild binary をよしなにダウンロードしてきてくれます．

```bash
% mix firmware
==> nerves
<snipped...>
==> rustler_precompiled
Compiling 4 files (.ex)
Generated rustler_precompiled app
==> zenohex
Compiling 13 files (.ex)

18:13:10.932 [debug] Copying NIF from cache and extracting to /Users/takasehideki/research/zenoh/zenohex_nerves/_build/rpi4_dev/lib/zenohex/priv/native/libzenohex_nif-v0.3.2-nif-2.15-aarch64-unknown-linux-gnu.so.tar.gz
Generated zenohex app
<snipped...>
==> zenohex_nerves
Compiling 2 files (.ex)
Generated zenohex_nerves app
|nerves| Building OTP Release...

* [Nerves] validating vm.args
* skipping runtime configuration (config/runtime.exs not found)
* creating _build/rpi4_dev/rel/zenohex_nerves/releases/0.1.0/vm.args
Updating base firmware image with Erlang release...
Copying rootfs_overlay: /Users/takasehideki/research/zenoh/zenohex_nerves/_build/rpi4_dev/nerves/rootfs_overlay
Copying rootfs_overlay: /Users/takasehideki/research/zenoh/zenohex_nerves/rootfs_overlay
Building /Users/takasehideki/research/zenoh/zenohex_nerves/_build/rpi4_dev/nerves/images/zenohex_nerves.fw...
Firmware built successfully! 🎉

Now you may install it to a MicroSD card using `mix burn` or upload it
to a device with `mix upload` or `mix firmware.gen.script`+`./upload.sh`.

```

## burn:fire:

これもいつものやつですね．
microSDカードにNervesファームウェアを焼きます．

```bash
% mix burn
==> nerves
==> zenohex_nerves

Nerves environment
  MIX_TARGET:   rpi4
  MIX_ENV:      dev

Use 14.65 GiB memory card found at /dev/rdisk4? [Yn] y
100% [====================================] 45.06 MB in / 54.37 MB out       
Success!
Elapsed time: 5.560 s

```

# 実行

## SSHアクセス

microSDカードを Raspberry Pi 4 に挿入し，Ethernetでよしなに接続しておきます．

これもいつものやつですね．電源を入れてちょっと待ってから `ssh nerves.local` で Nerves 上の IEx に入ります．

```bash
% ssh nerves.local
Warning: Permanently added 'nerves.local' (ED25519) to the list of known hosts.
Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
████▄▄    ▐███
█▌  ▀▀██▄▄  ▐█
█▌  ▄▄  ▀▀  ▐█   N  E  R  V  E  S
█▌  ▀▀██▄▄  ▐█
███▌    ▀▀████
zenohex_nerves 0.1.0 (22c33f4a-52b4-56c7-1d3e-159ed97caaa2) arm rpi4
  Serial       : 10000000b39a11d8
  Uptime       : 23.652 seconds
  Clock        : 2024-11-26 20:49:47 UTC (unsynchronized)
  Temperature  : 47.2°C

  Firmware     : Valid (A)               Applications : 41 started
  Memory usage : 152 MB (4%)             Part usage   : 288 MB (2%)
  Hostname     : nerves-11d8             Load average : 0.80 0.19 0.06

  eth0         : 192.168.11.100/24, fe80::e65f:1ff:fe65:8172/64
  usb0         : 172.31.166.73/30

Nerves CLI help: https://hexdocs.pm/nerves/iex-with-nerves.html

Toolshed imported. Run h(Toolshed) for more info.
iex(1)> 

```

## とりま Pub/Sub してみる

Zenohex リポジトリの README にある [Getting Started](https://github.com/biyooon-ex/zenohex?tab=readme-ov-file#getting-started) を :muishiki: にやってみます．

```elixir
iex(1)> {:ok, session} = Zenohex.open()
{:ok, #Reference<0.1198495918.1163788306.186120>}
iex(2)> {:ok, publisher} = Zenohex.Session.declare_publisher(session, "demo/example/test")
{:ok, #Reference<0.1198495918.1163788290.187264>}
iex(3)> {:ok, subscriber} = Zenohex.Session.declare_subscriber(session, "demo/**")
{:ok, #Reference<0.1198495918.1163788290.187298>}
iex(4)> Zenohex.Publisher.put(publisher, "Hello Zenoh Dragon")
:ok
iex(5)> Zenohex.Subscriber.recv_timeout(subscriber, 1000)
{:ok,
 %Zenohex.Sample{
   key_expr: "demo/example/test",
   value: "Hello Zenoh Dragon",
   kind: :put,
   reference: #Reference<0.1198495918.1163788306.186121>
 }}
```

やったぜいごいたぜっ！！

ちなリポジトリには，より Elixir 成分の感じられる [practical な Examples](https://github.com/biyooon-ex/zenohex/tree/main/lib/zenohex/examples) もご用意しております．

# 注意点というかお誘いというか

本稿執筆時点での Zenohex の最新版は v0.3.2 となります．

https://github.com/biyooon-ex/zenohex/releases/tag/v0.3.2

Zenoh への対応は [v0.11.0](https://github.com/eclipse-zenoh/zenoh/releases/tag/0.11.0) になります．
Zenoh は割りとバージョン番号にセンシティブ a.k.a 絶賛成長中で，異なるバージョン間では通信できないことが多いです．なので他ライブラリや言語モジュールと疎通させたい場合はご注意ください．

もちろん v1.0 対応は進行中でっす！

https://github.com/biyooon-ex/zenohex/pull/79

ですが,,, たいへん辛みがツラく,,, v0.11.0 to v1.0 はけっこう内部構造から変わってるんですよねぇ,,, 
Elixir と Rust と Rustler を完全に理解されている方がいましたら，ぜひとも手伝ってください！！

# まとめ

Zenoh の Elixir API である Zenohex を用いて Nerves で Pub/Sub をしてみました。簡単に Pub/Sub できました。

今回は NAT越えやネットワーク間通信をするための Zenoh ルータを用いていません。また Zenoh 独自の機能である pull や get も使っていません。今後、試していきたいと思います。

という既視感たっぷりな締めにて筆を置きたいと思います〜