zenoh-picoはどこまでpicoなマイコンで動作するのか

## はじめに

本記事はZenoh アドベントカレンダー1日目の記事です．

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

今日思い立って書いているので、検証が不十分な部分は多いかと思います。
間違っている部分などはご指摘いただけると助かります。

## Zenohとは

> **Zenoh** は **Ze**ro **n**etwork **o**ver**h**ead なプロトコルです．
> Pub/Sub 型の通信に加えて Store/Query なデータ管理と Compute な機能を備えています．
(Zenohアドベントカレンダーより)

また、以下の記事も参考になりました。

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


## モチベーション

以前、DDSという別の通信プロトコルを使っていたときに、マイコンから直接使えるらしいという情報を入手したので、Arduino Unoで試してみたらDue以上のスペックが必要ということが判明して挫折しました。Zenohでもzenoh-picoなるマイコン用のライブラリが存在するようなので、こちらはどこまで小さいマイコンで使用できるかを確認してみようと思います。

## PlatformIOでのライブラリの確認

さっそくPlatfromIOでZenoh-picoを探してみましょう。
まず、VSCodeでPlatformIOのホーム画面を開きます。
![Screenshot from 2024-12-01 12-26-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/2168b8e0-5ba7-dff8-7314-743fc4ef04e2.png)
ライブラリのタブからzenohを検索します。
![Screenshot from 2024-12-01 12-27-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/26479aab-402e-42aa-c9b4-f0ede71be14e.png)
ありました！

ライブラリのプラットフォームを確認すると、対応するマイコンを確認できます。
![Screenshot from 2024-12-01 12-32-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/7ccdee67-c07b-4574-e76a-e22edd5443a8.png)
どうやら様々な種類のマイコンに対応できそうです。
今回は手元で最も小型なArduino Nano Everyで試してみようと思います。

## PlatformIOでのプロジェクト作成とセットアップ

まず、PlatformIOのホーム画面に戻ります。
![Screenshot from 2024-12-01 12-26-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/66264b29-d8de-14f8-d07c-755ade584cef.png)
[Quick Access]から[+ New Project]を選択します。
![Screenshot from 2024-12-01 12-26-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/c350eb92-367f-7c7e-b054-fcadd5e024a7.png)
適当なプロジェクト名（今回はZenohTest）と適切なマイコンを選択して[Finish]ボタンを押せばプロジェクトが作成できます。

次にライブラリの設定を行います。
さきほどのライブラリ検索を再度実行して、ライブラリの詳細画面を開きます。
![Screenshot from 2024-12-01 14-21-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/e54bcd00-c3be-5ed9-cfb7-faafc5c3ac26.png)
画面中央に見えている[Add to Project]ボタンを押します。
![Screenshot from 2024-12-01 12-27-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/0d5800c0-7c7e-a185-92c4-9c0f2caaff79.png)
さきほど作成したプロジェクトを指定して[Add]ボタンを押しましょう。
すると、ライブラリがプロジェクトに導入されるはず……
![Screenshot from 2024-12-01 12-29-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/df57e8d7-d3a1-3feb-8edf-061082fe1553.png)
……どうやらLinuxは対応していないようです。
落ち着いて、GutHubから最新のzenoh-picoを導入してみましょう。
現在開いているplatformio.iniファイルを以下のように書き換えます。
```platformio.ini
[env:nano_every]
platform = atmelmegaavr
board = nano_every
framework = arduino
lib_deps = https://github.com/eclipse-zenoh/zenoh-pico.git
build_flags =
    -D Z_LINK_SERIAL=1
    -D ZENOH_DEBUG=0 ; no log
    ; -D ZENOH_DEBUG=3 ; debug log
    -D Z_BATCH_UNICAST_SIZE=32767
    -D Z_FRAG_MAX_SIZE=32767
    -D ZENOH_SERIAL_IDX=0 ; USB Port
    ; -D ZENOH_SERIAL_IDX=1 ; Pin9, 10
    ; -D ZENOH_SERIAL_IDX=2 ; Pin16, 17
    -D ZENOH_SERIAL_BAUDRATE=115200
    -D ARDUHAL_LOG_LEVEL=0 ; ARDUHAL_LOG_LEVEL_NONE
    ; -D ARDUHAL_LOG_LEVEL=1 ; ARDUHAL_LOG_LEVEL_ERROR
```
こちらの設定は下記サイトを参考に最新のzenoh-picoを導入できるように変更したものです。
また、シリアル通信を有効にするビルドオプションを加えています。

https://zenn.dev/fate_shelled/scraps/31fca73cc4c191

変更して保存すると自動で環境設定が開始します。
![Screenshot from 2024-12-01 14-31-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/f1f0bc40-c4d1-44ff-16b0-a4b68024cf9a.png)
うまくライブラリが導入できました！

## プログラムの作成

上記参考サイトのソースコードを元に、最新のAPIで書き直してみます。
```main.cpp
#include <Arduino.h>
#include <string.h>

#include <zenoh-pico.h>

#define MODE Z_CONFIG_MODE_CLIENT
#if ZENOH_SERIAL_IDX == 0
/* UART0 */
#define ZENOH_SERIAL_TX 1
#define ZENOH_SERIAL_RX 3
#elif ZENOH_SERIAL_IDX == 1
/* UART1 */
#define ZENOH_SERIAL_TX 10
#define ZENOH_SERIAL_RX 9
#elif ZENOH_SERIAL_IDX == 2
/* UART2 */
#define ZENOH_SERIAL_TX 17
#define ZENOH_SERIAL_RX 16
#endif

#define SUB_KEYEXPR "demo/example/zenoh-python-pub"
#define PUB_KEYEXPR "demo/example/zenoh-pico-pub"
#define PUB_VALUE "[ARDUINO]{ESP32} Pub from Zenoh-Pico via Serial!"

z_owned_session_t session_;
z_owned_publisher_t pub_;
z_owned_subscriber_t sub_;

void data_handler(z_loaned_sample_t *sample, void *arg)
{
  z_view_string_t keystr;
  z_keyexpr_as_view_string(z_sample_keyexpr(sample), &keystr);
  z_owned_string_t value;
  z_bytes_to_string(z_sample_payload(sample), &value);

  // Serial.print(" >> [Subscription listener] Received (");
  // Serial.write(z_string_data(z_view_string_loan(&keystr)), z_string_len(z_view_string_loan(&keystr)));
  // Serial.print(", ");
  // Serial.write(z_string_data(z_string_loan(&value)), z_string_len(z_string_loan(&value)));
  // Serial.println(")");

  z_string_drop(z_string_move(&value));
}

z_owned_session_t open_session()
{
  // https://zenoh.io/blog/2022-08-12-zenoh-serial/
  char peer[100] = {};
  snprintf(
      peer, sizeof(peer),
      "serial/%d.%d#baudrate=%d", ZENOH_SERIAL_TX, ZENOH_SERIAL_RX, ZENOH_SERIAL_BAUDRATE);
  // log_i(peer);

  // Initialize Zenoh Session and other parameters
  z_owned_config_t config;
  z_config_default(&config);

  zp_config_insert(z_config_loan_mut(&config), Z_CONFIG_MODE_KEY, MODE);
  zp_config_insert(z_config_loan_mut(&config), Z_CONFIG_CONNECT_KEY, peer);

  // Open Zenoh session
  // log_i("Open session!\n");

  // z_owned_session_t session = z_open(z_config_move(&config));
  z_owned_session_t session;
  if (z_open(&session, z_config_move(&config), NULL) < 0)
  {
    // Serial.println("Unable to open session!");
    while (1)
    {
      ;
    }
  }
  // log_i("OK\n");

  // Start the receive and the session lease loop for zenoh-pico
  if (zp_start_read_task(z_session_loan_mut(&session), NULL) < 0 || zp_start_lease_task(z_session_loan_mut(&session), NULL) < 0)
  {
    // Serial.println("Unable to start read and lease tasks\n");
    z_session_drop(z_session_move(&session));
    while (1)
    {
      ;
    }
  }
  return session;
}

z_owned_publisher_t create_publisher(z_owned_session_t session)
{
  // log_i("Declaring publisher for '%s'...", PUB_KEYEXPR);
  z_view_keyexpr_t ke;
  z_owned_publisher_t pub;
  z_view_keyexpr_from_str_unchecked(&ke, PUB_KEYEXPR);
  if (z_declare_publisher(z_session_loan(&session), &pub, z_view_keyexpr_loan(&ke), NULL) < 0)
  {
    // log_i("Unable to declare publisher for key expression!\n");
    while (1)
    {
      ;
    }
  }
  // log_i("OK\n");
  return pub;
}

z_owned_subscriber_t create_subscription(z_owned_session_t session)
{
  // log_i("Declaring subscriptions (on '%s')...", SUB_KEYEXPR);

  z_owned_closure_sample_t callback;
  z_closure_sample(&callback, data_handler, NULL, NULL);
  z_view_keyexpr_t ke;
  z_owned_subscriber_t sub;
  z_view_keyexpr_from_str_unchecked(&ke, SUB_KEYEXPR);
  if (z_declare_subscriber(z_session_loan(&session), &sub, z_view_keyexpr_loan(&ke), z_closure_sample_move(&callback),
                           NULL) < 0)
  {
    Serial.println("Unable to declare subscriber.");
    while (1)
    {
      ;
    }
  }
  // log_i("OK!");
  return sub;
}

void close_session(z_owned_session_t session, z_owned_publisher_t pub, z_owned_subscriber_t sub)
{
  // log_i("Closing Zenoh Session...");
  z_undeclare_publisher(z_publisher_move(&pub));

  z_undeclare_subscriber(z_subscriber_move(&sub));

  // Stop the receive and the session lease loop for zenoh-pico
  zp_stop_read_task(z_session_loan_mut(&session));
  zp_stop_lease_task(z_session_loan_mut(&session));

  z_close_options_t options;
  z_close(z_session_loan_mut(&session), &options);
  // log_i("OK!\n");
}

void loop()
{
  static int32_t counter = 0;
  char buf[sizeof(PUB_VALUE) + 7];
  int ret = 0;
  snprintf(buf, sizeof(buf), "%s [%4d]", PUB_VALUE, counter);

  z_publisher_put_options_t options;
  z_publisher_put_options_default(&options);
  options.encoding = z_move(const_cast<z_owned_encoding_t &>(ZP_ENCODING_TEXT_PLAIN));

  // log_i("Publish Data ('%s': '%s')...", PUB_KEYEXPR, buf);
  z_owned_bytes_t payload;
  z_bytes_copy_from_str(&payload, buf);
  ret = z_publisher_put(z_publisher_loan(&pub_), z_move(payload), &options);
  if (ret == 0)
  {
    // log_i("OK!\n");
  }
  else
  {
    // log_i("NG!\n");
    return;
  }

  counter += 1;
  z_sleep_s(2);
}

void setup()
{
  // log_i("zenoh-pico-example!");
  session_ = open_session();
  pub_ = create_publisher(session_);
  sub_ = create_subscription(session_);
}
```
最新のAPIでは、APIの名前が変わっていたり、zenoh-pico.hをextern "C"で囲ってはいけないといった部分で変更が見られました。

ソースコード側でおおよそエラーがなくなってきたあたりでふとVSCodeの問題タブを確認すると以下の問題が目に止まりました。
![Screenshot from 2024-12-01 14-37-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/694b338b-f3fe-ea1d-f705-6e015c2f9e1f.png)
試しにplatform_common.hを確認してみます。
![Screenshot from 2024-12-01 14-38-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326389/2906abe9-3554-dff7-8546-c2320a1fbf19.png)
上記の通り、このソースコードでサポートしているのは、ArduinoだとESP32とOpenCRだけだということがわかりました。

## おわりに

今回の検証では、PlatformIOの表記からArduino Nano EveryでZenohを試してみようと画策しましたが、実際に対応しているArduinoはESP32とOpenCRだけというオチでした。環境設定まで完了できたのに、ビルドが通らないのはなかなか辛いです。流石にArduino Nanoレベルで使える必要はないかもしれないですが、Unoくらいのサイズで通信できると良いのではないでしょうか。今後のZenohの発展に期待しています！