はじめてNerves(13) Elixir API の Zenohex を使って Zenoh で Pub/Sub してみる

これは [#NervesJP Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nervesjp) の23日目です。昨日は [@myasu](https://qiita.com/myasu) さんの [ElixirでGUI・Scenic [新・入門編]](https://qiita.com/myasu/items/e15ed98d08e2f2c804c3) でした。

# はじめに
去年あたりから [Zenoh](https://zenoh.io) という言葉を聞くようになりました。どうやら素敵な Pub/Sub のプラットフォームのようです。IoT でのプログラミングに Pub/Sub は欠かせないので大変気になります。

それでもって本家には Elixir 用の API がなかったのです。が、 [@Shintaro_Hosoai](https://qiita.com/Shintaro_Hosoai) さんが [Zenohex](https://hex.pm/packages/zenohex) という Elixir 用の API を手掛けてくれてますので、それを使ってみることにしましょう。なお、おそらく `Zenohex` の発音は「ゼノヘックス」ではなく「ゼノ〜・イーエックス」だと思います。

なお Zenoh がどう素晴らしそうなのかは、やはり[@Shintaro_Hosoai](https://qiita.com/Shintaro_Hosoai) さんがお書きになってる [Pub/Sub通信ライブラリZenohのススメ](https://qiita.com/Shintaro_Hosoai/items/0bde489cde43a00d6f96) を御覧ください。

# いごかしてみる
まずは zenohex の環境を作ります。いつものお作法通りです。

```shell
% mix new zenoh
% cd zenoh
```

`mix.exs` に1行追加します。

```diff_elixir:mix.exs
  defp deps do
    [
 +     {:zenohex, "~> 0.1.3"}
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
    ]
  end
```
そして依存関係を解決します。
```
% mix deps.get
```
これで準備ができました。

## Pub/Sub 通信させてみる
2つのpublisherと2つのsubscriberを同じ key（トピック）で通信してみます[^key-value]。以下では4つのターミナルを使います。全部同一のマシン上でいごかします。トピックは `afo` です。

1. terminal 1: Pub側
1. terminal 2: Pub側
1. terminal 3: Sub側
1. terminal 4: Sub側

[^key-value]: 通常の Pub/Sub で、トピック・メッセージと呼ぶ通信の単位を、Zenoh ではちょっとDBっぽく `key` と `value` と読んでます。

### 全てのターミナルで iex を起動する


```shell:terminal 1
% iex -S mix
Erlang/OTP 26 [erts-14.2.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
```

```shell:terminal 2
%iex -S mix
Erlang/OTP 26 [erts-14.2.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

```shell:terminal 3
%iex -S mix 
Erlang/OTP 26 [erts-14.2.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

```shell:terminal 4
% iex -S mix
Erlang/OTP 26 [erts-14.2.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
```

### Publisher の準備をする
さきに Pub 側の準備をします。
まず `Zenohex.open/0` 関数で session を作ります。

```exs:terminal 1
iex(1)> session = Zenohex.open
#Reference<0.1263928079.4256563207.212580>
```
つぎに Publisher の宣言を `Session.declare_publisher/2` で行います。
- 第1引数: さきほどの session の #ref
- 第2引数: トピック (key)

今回はトピック (key) を `"afo"` にします。

```exs:terminal 1
iex(2)> {:ok, publisher} = Session.declare_publisher(session, "afo")
{:ok, #Reference<0.1263928079.4256563201.214873>}
```
もうひとつの Publisher 用のターミナル2でも同じことをします。
```exs:terminal 2
iex(1)> session = Zenohex.open
#Reference<0.1869669285.1303773189.148236>
iex(2)> {:ok, publisher} = Session.declare_publisher(session, "afo")
{:ok, #Reference<0.1869669285.1303773187.149528>}
```

### Subscriber の準備をする
つぎに Sub 側の準備をします。
まず `Zenohex.open/0` 関数で session を作ります。

```exs:terminal 3
iex(1)> session = Zenohex.open
#Reference<0.1665375593.2109341703.230733>
```

つぎに　Subscriber の宣言を `Session.declare_subscriber/3` で行います。
- 第1引数: さきほどの session の #ref
- 第2引数: トピック (key)
- 第3引数: コールバック関数

先程の publisher の場合より引数が増えてることに注意です。

```exs:terminal 3
iex(2)> Session.declare_subscriber(session, "afo", fn m -> IO.inspect(m) end)
:ok
```
この宣言で、該当するトピック(key)で pub されたら、コールバック関数が呼ばれます。そのときのメッセージ(value)はコールバック関数の引数に渡されます。
今回は貰った値を `IO.inspect/2` でそのままターミナルに出力します。

もうひとつの Subscriber 用のターミナル4でも同様のことをします。
```exs:terminal 4
iex(1)> session = Zenohex.open
#Reference<0.34013382.2646212610.123859>
iex(2)> Session.declare_subscriber(session, "afo", fn m -> IO.inspect(m) end)
:ok
```

### 通信してみる
では Pub/Sub 通信をしてみましょう。まず、ターミナル1で発信します。

```exs:terminal 1
iex(3)> Publisher.put(publisher, "foo")
:ok
```
```exs:terminal 3
"foo"
```
```exs:terminal 4
"foo"
```
Subscriber のあるターミナル3とターミナル4とが受け取って出力してます。

こんどはターミナル2から発信します。
```exs:terminal 2
iex(3)> Publisher.put(publisher, "bar")
:ok
```

```exs:terminal 3
"bar"
```
```exs:terminal 4
"bar"
```
こちらもそのまま出力されました。

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

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

さて、明日の [#NervesJP Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nervesjp) の記事は [@mitsuhiro](https://qiita.com/32hero) さんの「Giocci on Nerves　たぶん。」です。お楽しみに！

# 参考文献
- [はじめてなElixir(0)](https://qiita.com/kikuyuta/items/3c1798d15508040ccaaf)
  - [はじめてな Elixir(29) Registry で Pub/Sub する](https://qiita.com/kikuyuta/items/a05ce2516260d7ef27a3)
  - [はじめてな Elixir(31) Swarm で Pub/Sub する](https://qiita.com/kikuyuta/items/f337877c65186d532187)
  - [はじめてな Elixir(32) Syn (v2.1) で Pub/Sub する](https://qiita.com/kikuyuta/items/5bd9e8ea9cf6bca3f931)
  - [はじめてな Elixir(33) Syn (v3.3) で Pub/Sub する](https://qiita.com/kikuyuta/items/85645efb7562aaccbdf7)
- [Zenoh](https://zenoh.io)
  - [Eclipse ZenohでNAT越えROS2通信](https://qiita.com/Shintaro_Hosoai/items/a2f6c253a5b4599b056c)
  - [Pub/Sub通信ライブラリZenohのススメ](https://qiita.com/Shintaro_Hosoai/items/0bde489cde43a00d6f96)
  - [pub/sub通信ライブラリZenohを試す](https://qiita.com/myasu/items/ae53aba8ecac1cd3596c)
  - [Elixir Moduleの理解・分解・再構築](https://qiita.com/Shintaro_Hosoai/items/e8edd18513be8b043219)
  - [RustlerでRustのzenohライブラリをElixirに移植する勘所](https://qiita.com/Shintaro_Hosoai/items/eeaa22dd2fb08c8feec2)
  - [2023年新春兎年Nerves/Elixirフォネティックコード](https://qiita.com/kikuyuta/items/58da4ef8104fdd3f581a)