あ、どうも
いかこうです。
初投稿です。
zenohのアドベントカレンダーが開催されるということで参加せさていただきました。
僕自身は、zenohバリバリ使ってるよ!!みたいな人ではないのですが、zenohにはかなり興味を持っているので何か日本のzenohコミュニティに貢献したいなと思いこの記事を書きました。
まずは、一番基本的なpubsub通信に関する内容を取り上げようと思います。
この記事が、僕を含めたzenohワカラナイ人たちの助けになることを期待しています。
zenohってなんゼノー??????(極寒)
zenohの開発元であるZettaScaleはzenohを以下のように解説しています。
「Zenoh、静的データ、動的データおよびコンピューティングを統合する Pub/Sub/Query プロトコルです。従来のPub/Subを地域分散ストレージやクエリ、演算(コンピューティング)とエレガントに組み合わせながらも、どのメインストリームスタックよりもはるかに高いレベルの時間とスペースの効率を保持しています。」
いかこう的にまとめるとこんな感じ。
「ロボティクスやIoT分野で使われる通信プロトコルをいい感じにまとめたよ〜」
他にもzenohには以下のような特徴があります。
- 多様な通信方式のサポート: メッシュ、peer2peer、ルーテッド、ブローカード
- 結構早い
- 多様なネットワークレイヤ: TCP、UDP、Ethernet、Bluetooth
- 組み込みデバイスへの対応(zenoh-pico)
- 多様な言語対応: C, C++, Rust, Python, Elixirなど
この人の記事も非常にわかりやすいです。こっちの方が表があったりして直感的かも
https://qiita.com/Shintaro_Hosoai/items/0bde489cde43a00d6f96
もうzenohだけやっとけば何でも通信できんじゃね!?
注意: zenohにはバージョンがあり、場合によっては互換性がなかったりプログラムが全然違ってたりするので、できる限り揃えることをおすすめします。
現状では、1.0.3、1.0.4では互換性があることが確認できています。
Rustでzenoh
細かいことは置いといてzenohを使ってみましょう!!まずはRustから!!!
githubリポジトリ
注意: いかこうはRustナニモワカラナイ人です
当たり前ですが、Rust環境はインストールしておいてください。
1. インストール
ここでは、zenoh v1.0.3
を使用しました。
echo "deb [trusted=yes] https://download.eclipse.org/zenoh/debian-repo/ /" | sudo tee -a /etc/apt/sources.list.d/zenoh.list > /dev/null
sudo apt update
sudo apt install zenoh
brew tap eclipse-zenoh/homebrew-zenoh
brew install zenoh
マニュアルインストールはこのページからzipファイルをダウンロード
この場合は以下のコマンドでビルドする必要があります。
cd zenoh # zenohフォルダに移動
rustup update # Rustのツールチェーンをアップデート
cargo build --release --all-targets # ビルド
2. プロジェクトの作成
まず、Rustのプロジェクトを作成します。
cargo new zenoh_pubsub
Rustでは、実行ファイルを複数作成する場合はbin/
にまとめる慣習?があるらしいのでこれに則って以下のようなフォルダ構成にします。
zenoh_pubsub/
├── src/
│ └── bin/
│ ├── pub.rs
│ └── sub.rs
├── target/
├── Cargo.lock
└── Cargo.toml
Cargo.toml
を編集して使用するクレートを追加します。Rustで使うパッケージはクレートって呼ぶらしい。
[package]
name = "zenoh_pubsub"
version = "0.1.0"
edition = "2021"
[dependencies]
zenoh = "1.0.3"
tokio = { version = "1", features = ["full"] }
3. pubsub通信
簡単なパブリッシャーとサブスクライバーを書いてみましょう!!
use std::thread;
use std::time::Duration;
#[tokio::main]
async fn main(){
let session = zenoh::open(zenoh::config::Config::default()).await.unwrap(); // zenohセッションの開始
let keyexpr = "test".to_string(); // keyを定義
let publisher = session.declare_publisher(&keyexpr).await.unwrap(); // パブリッシャーの定義
let msg = "Hello, World"; // 送信データ
loop{
publisher.put(msg).await.unwrap(); // データのパブリッシュ
println!("published: {:?}", msg);
thread::sleep(Duration::from_secs(1)); // 1秒間スリープ
}
}
#[tokio::main]
async fn main(){
let session = zenoh::open(zenoh::config::Config::default()).await.unwrap(); // zenohセッションの開始
let keyexpr = "test".to_string(); // keyの定義
let subscriber = session.declare_subscriber(&keyexpr).await.unwrap(); // サブスクライバーの定義
while let Ok(sample) = subscriber.recv() {
println!("Received: {:?}", sample); // 受信データを表示
let key = sample.key_expr().to_string(); // 受信データからkeyを取得
let payload = sample.payload().try_to_string().unwrap_or_else(|e| e.to_string().into()); // 受信データからメインデータを取得
println!("key: {}", key);
println!("payload: {}", payload);
}
}
ではいごかします。プロジェクトのルートディレクトリで
cargo build
問題なくビルドが完了したら、ターミナルを二つ開いてそれぞれのプログラムを実行します。
二つのプログラム間で通信していることが確認できると思います。
$ cargo run --bin pub
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.03s
Running `target/debug/pub`
published: "Hello, World"
published: "Hello, World"
published: "Hello, World"
published: "Hello, World"
$ cargo run --bin sub
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/sub`
Received: Sample { key_expr: ke`test`, payload: ZBytes(ZBuf { slices: [[48, 65, 6c, 6c, 6f, 2c, 20, 57, 6f, 72, 6c, 64]] }), kind: Put, encoding: Encoding(Encoding { id: 0, schema: None }), timestamp: None, qos: QoS { inner: QoS { priority: Data, congestion: Drop, express: false } }, attachment: None }
key: test
payload: Hello, World
Received: Sample { key_expr: ke`test`, payload: ZBytes(ZBuf { slices: [[48, 65, 6c, 6c, 6f, 2c, 20, 57, 6f, 72, 6c, 64]] }), kind: Put, encoding: Encoding(Encoding { id: 0, schema: None }), timestamp: None, qos: QoS { inner: QoS { priority: Data, congestion: Drop, express: false } }, attachment: None }
Pythonでzenoh
次は同じくzenoh v1.0.3
をpythonで書いていきます。
githubリポジトリ
1. インストール
pythonでは二つ方法があります。どちらも事前に仮装環境を作っておくことを推奨します。
-
pip install
普通にpip install
で入ります非推奨pip install eclipse-zenoh
しかし、このままでは最新のバージョンがインストールされてしまうので、バージョンを指定した方が良いでしょう
推奨$ pip index versions eclipse-zenoh # インストール可能なバージョンの確認 eclipse-zenoh (1.0.4) Available versions: 1.0.4, 1.0.3, 1.0.2, 1.0.1, 1.0.0, 0.11.0 INSTALLED: 1.0.3 LATEST: 1.0.4 $ pip install eclipse-zenoh==1.0.3 # バージョン指定でインストール
-
マニュアルインストール
git clone
してビルドする方法です。Rust環境が必要です。git clone -b 1.0.3 https://github.com/eclipse-zenoh/zenoh-python.git # バージョン1.0.3でクローン cd zenoh-python rustup update pip install -r requirements-dev.txt which maturin # maturinていうビルドツールのパスを取得 export PATH=$PATH:<maturinのパス> # ↑で取得したパスを環境変数に追加する(ここで間違えて上書きしちゃって大変なことになった) maturin develop --release
2. pubsub通信
以下にpubsubプログラムを示します。
import time
import zenoh
def main():
conf = zenoh.Config() # zenohの設定
session = zenoh.open(conf) # zenohセッションの開始
key = "hoge" # データを判別するkeyの定義
pub = session.declare_publisher(key) # パブリッシャーの定義
interval = 1 # 1秒間隔でパブリッシュ
cnt = 0
while True:
msg = "msg from '{key}'. cnt={cnt}".format(key=key, cnt=cnt) # 送信データの作成
pub.put(msg) # データをパブリッシュ
print(f"Published!! msg={msg}")
time.sleep(interval)
cnt += 1
if __name__ == "__main__":
main()
import zenoh
def listener(sample):
"""
サブスクライバのコールバック関数
"""
# print(f"sample info: {sample=}") # sampleの構造を表示
key = sample.key_expr # キーの部分を取得
payload = sample.payload.to_string() # データの部分を取得
print(f"Received '{payload}' from '{key}'")
def main():
conf = zenoh.Config() # zenohの設定
session = zenoh.open(conf) # zenohセッションの開始
key = "hoge" # サブスクライブするキーの定義
sub = session.declare_subscriber(key, listener) # キーを指定してサブスクライブ
while True:
pass
if __name__ == "__main__":
main()
あとはそれぞれ実行するだけです。pythonは簡単でいいですね
$ python pub.py
Published!! msg=msg from 'hoge'. cnt=0
Published!! msg=msg from 'hoge'. cnt=1
Published!! msg=msg from 'hoge'. cnt=2
Published!! msg=msg from 'hoge'. cnt=3
Published!! msg=msg from 'hoge'. cnt=4
Published!! msg=msg from 'hoge'. cnt=5
Published!! msg=msg from 'hoge'. cnt=6
Published!! msg=msg from 'hoge'. cnt=7
Published!! msg=msg from 'hoge'. cnt=8
Published!! msg=msg from 'hoge'. cnt=9
$ python sub.py
Received 'msg from 'hoge'. cnt=7' from 'hoge'
Received 'msg from 'hoge'. cnt=8' from 'hoge'
Received 'msg from 'hoge'. cnt=9' from 'hoge'
Received 'msg from 'hoge'. cnt=10' from 'hoge'
Received 'msg from 'hoge'. cnt=11' from 'hoge'
Received 'msg from 'hoge'. cnt=12' from 'hoge'
Received 'msg from 'hoge'. cnt=13' from 'hoge'
Received 'msg from 'hoge'. cnt=14' from 'hoge'
C++でzenoh
c++が一番めんどかったです。zenoh v1.0.4
で動作確認しました。
githubリポジトリ
1. インストール
git clone
してビルドします。
git clone --recursive -b 1.0.4 https://github.com/eclipse-zenoh/zenoh-cpp.git
cd zenoh-cpp
mkdir build
ここで、READMEによると
「By default it is expected that you have zenoh-c installed. If you want to install for zenoh-pico backend or for both (or to not specify any backend), please set ZENOHCXX_ZENOHC or ZENOHCXX_ZENOHPICO Cmake variables toON or OFF accordingly. Notice that at least one of the backends is required for using the library and/or building tests and examples.
Use option CMAKE_INSTALL_PREFIX for specifying installation location. Without this parameter installation is performed to default system location /usr/local which requires root privileges.」
デフォルトでは zenoh-c がインストールされているものとします。zenoh-pico バックエンドまたはその両方をインストールしたい場合 (またはバックエンドを指定したくない場合) は、ZENOHCXX_ZENOHC または ZENOHCXX_ZENOHPICO Cmake 変数を適宜 ON または OFF に設定してください。ライブラリを使用したり、テストやサンプルをビルドしたりするには、少なくともどちらかのバックエンドが必要です。
インストール場所を指定するには CMAKE_INSTALL_PREFIX オプションを使用します。このパラメータを指定しないと、インストールはデフォルトのシステム・ロケーション /usr/local に行われ、root 権限が必要になります。(DeepL翻訳)
なんかバックエンドってのを設定してビルドするみたいですね。
どうやら、zenoh-cpp
のなかでzenoh-c
を使用してるやつとzenoh-pico
を使用してるやつがあるっぽい??(間違ってるかも)
ってことで、zenoh-c
のビルドもした方が良さそうです。zenoh-c
はzenoh-cpp
のリポジトリに含まれてます。
cd zenoh-c
rustup update
mkdir build
cd build
cmake ..
cmake --build . --config Release
cmake --build . --target install
zenoh-cpp
に戻ってビルドします。今回はバックエンドにzenoh-c
を使用してビルドします。
cd build
cmake .. -DZENOHCXX_ZENOHC=ON -DZENOHCXX_ZENOHPICO=OFF -DCMAKE_INSTALL_PREFIX=~/.local
cmake --install .
cmake --build . --target examples # サンプルコードのビルド
2. 動作確認
色々ビルドしたので、プログラムを書く前にちゃんと動作するか確認したくなります。
サンプルコードの実行ファイルはzenoh-cpp/build/examples/zenohc
にあります。
./z_pub test # keyをtestでパブリッシュ
./z_sub test # testをサブスクライブ
3. pubsub通信
動作確認ができたら早速プログラムを書いていきましょう。
フォルダ構成はこんな感じ
zenoh_cpp_test/
├── build/
├── include/
├── lib/
│ └── zenoh-cpp/
├── src/
│ ├── pub.cpp
│ └── sub.cpp
└── CMakeLists.txt
#include <iostream>
#include <thread>
#include "zenoh.hxx"
using namespace zenoh;
int main(int argc, char **argv)
{
Config config = Config::create_default();
auto session = Session::open(std::move(config));
std::string key = "test";
std::string payload = "Hello, World";
auto publisher = session.declare_publisher(KeyExpr(key.c_str()));
while (1)
{
publisher.put(payload.c_str());
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
#include <iostream>
#include "zenoh.hxx"
using namespace zenoh;
void listener(const Sample &sample)
{
std::cout << "Received: " << sample.get_payload().as_string() << std::endl;
}
int main(int argc, char **argv)
{
Config config = Config::create_default();
auto session = Session::open(std::move(config));
std::string key = "test";
auto subscriber = session.declare_subscriber(KeyExpr(key.c_str()), &listener, closures::none);
while (1)
{
continue;
}
}
cmake_minimum_required(VERSION 3.5)
project(zenoh_cpp_test)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PUBLISHER_EXECUTABLE_NAME pub)
set(SUBSCRIBER_EXECUTABLE_NAME sub)
find_package(zenohc REQUIRED)
find_package(zenohcxx REQUIRED)
# 実行ファイルの生成
add_executable(${PUBLISHER_EXECUTABLE_NAME} src/pub.cpp)
add_executable(${SUBSCRIBER_EXECUTABLE_NAME} src/sub.cpp)
# ライブラリのリンク
target_link_libraries(${PUBLISHER_EXECUTABLE_NAME} zenohcxx::zenohc)
target_link_libraries(${SUBSCRIBER_EXECUTABLE_NAME} zenohcxx::zenohc)
cmakeビルドです。
mkdir build
cd build
cmake ..
make
ビルドに成功するとbuild/
ないにpub
とsub
が実行ファイルとして作成されるはずです。
./pub
$ ./sub
Received: Hello, World
Received: Hello, World
Received: Hello, World
Received: Hello, World
Received: Hello, World
おわりに
読みずらい文章を最後まで読んでいただきありがとうございます。ここまで読んでくれたあなたはいい人です。
zenoh-cpp
で結構苦労しましたが、動いてよかったです。そして、アドベントカレンダーの期日に間に合いそうで一安心です。
qiitaの記事を書くのはこれが初めてですが、結構楽しかったです。これからも積極的に書いていこうと思います。
ここで取り上げた内容はzenoh
の本の一部なので自分が理解でき次第、他の内容も取り上げたいです。
zenoh
これからめちゃくちゃキそうなのでみんなで使っていきましょう