Eclipse Zenoh 在 Rust 和 JVM 環境中提供了強大的核心實作,還支援其他的綁定,以滿足多樣的應用需求。今天來介紹兩個值得注意的專案,zenoh-pico 和 zenoh-ts,將 Zenoh 的觸角延伸到嵌入式設備和網路應用。
zenoh-pico 提供了一個輕量、
原生的 C 實作,專為嵌入式系統、
微控制器和即時作業系統(RTOS)所優化。
zenoh-pico 與主要的 Rust Zenoh stack 相容,為需要 Zenoh 能力的低資源平台搭建了橋梁。
#include <zenoh-pico.h>
int main() {
// 定義主題關鍵字與訊息內容
const char *keyexpr = "demo/example/zenoh-pico-pub";
const char *value = "Hello from Zenoh-Pico!";
const char *mode = "client"; // 以 client(連線至 router/peer)模式運行
/*
* 步驟 1:初始化 Zenoh 設定
* zenoh-pico 採用所有權管理資源,需妥善釋放
*/
z_owned_config_t config;
z_config_default(&config); // 取得預設設定
// 設定模式為 "client"
// 其他模式: "peer"(P2P)、"router"(基礎設施節點)
zp_config_insert(z_loan_mut(config), Z_CONFIG_MODE_KEY, mode);
/*
* 步驟 2:開啟 Zenoh 連線(Session)
* Session 代表與 Zenoh 網路的連線
* z_move() 將 config 移交給 z_open() 管理
*/
printf("Opening session...\n");
z_owned_session_t s;
if (z_open(&s, z_move(config), NULL) < 0) {
printf("Unable to open session!\n");
return -1;
}
/*
* 步驟 3:啟動背景任務(zenoh-pico 特有)
* - Read task: 處理網路接收訊息
* - Lease task: 透過週期性心跳維持 session 存活
* 缺一不可,否則功能異常
*/
if (zp_start_read_task(z_loan_mut(s), NULL) < 0 ||
zp_start_lease_task(z_loan_mut(s), NULL) < 0) {
printf("Unable to start read and lease tasks\n");
z_drop(z_move(s)); // 失敗時釋放 session
return -1;
}
/*
* 步驟 4:宣告 Publisher
* Publisher 用來在指定主題發送資料
* z_loan(s) 借用 session,不轉移所有權
*/
printf("Declaring Publisher on '%s'...\n", keyexpr);
z_owned_publisher_t pub;
if (z_declare_publisher(&pub, z_loan(s), z_keyexpr(keyexpr), NULL) < 0) {
printf("Unable to declare Publisher for key expression!\n");
return -1;
}
/*
* 步驟 5:發佈資料
* 將訊息傳送給所有訂閱者
* 資料以 raw bytes (uint8_t*) 傳送,需標明長度
*/
printf("Putting Data ('%s': '%s')...\n", keyexpr, value);
z_publisher_put_options_t options;
z_publisher_put_options_default(&options);
if (z_publisher_put(z_loan(pub), (const uint8_t *)value, strlen(value), &options) < 0) {
printf("Oh no! Publication has failed...\n");
}
/*
* 步驟 6:資源清理
* zenoh-pico 強制顯式釋放所有擁有型資源
* 順序需與建立時相反
*/
z_drop(z_move(pub)); // 釋放 publisher
zp_stop_read_task(z_loan_mut(s)); // 停止背景接收任務
zp_stop_lease_task(z_loan_mut(s));// 停止背景 lease 任務
z_drop(z_move(s)); // 釋放 session
return 0;
}
詳細的建構說明與多種嵌入式平台示例,請參閱 github repository。
zenoh-pico 提供多平台支援與專屬建構配方:
# CMake 編譯(Unix/Linux)-編譯函式庫與範例
cd zenoh-pico
make # 建置函式庫與範例
sudo make install # 系統內安裝 headers 和函式庫
# PlatformIO(嵌入式系統自動依賴管理)
pio run --target upload # 編譯韌體並上傳微控制器
# Arduino IDE 整合(適合 Arduino 開發)
# 透過 Library Manager 搜尋「zenoh-pico」安裝函式庫
# 並於程式中引入:#include <zenoh-pico.h>
zenoh-ts 讓網頁開發者可透過 TypeScript 使用 Zenoh 的 pub/sub、store 及 query 機能,透過 WebSocket 與 Zenoh router 連線,支援瀏覽器與 Node.js 類環境。
import { Session, Config, KeyExpr, Publisher, Subscriber, Encoding, Priority } from "@eclipse-zenoh/zenoh-ts";
async function main() {
console.log("Opening session...");
/*
* 步驟 1:與 Zenoh Router 建立 WebSocket 連線
* zenoh-ts 透過 WebSocket 連至 zenoh-bridge-remote-api 或啟用 remote-api plugin 的 zenohd
* 格式:"ws/主機:埠號" - 連結 WebSocket 端點
*/
const session = await Session.open(new Config("ws/127.0.0.1:10000"));
// 定義主題 key 與訊息內容
// KeyExpr.autocanonize() 會優化 key expr 以提升網路效率
const keyExpr = KeyExpr.autocanonize("demo/example/zenoh-ts-pub");
const payload = "Hello from Zenoh-TS!";
/*
* 步驟 2:宣告 Publisher 並設定選項
* Publisher 會將資料發送給符合 key expr 的訂閱者
* 選項可設訊息編碼、網路優先度、是否 express(低延遲,不等壅塞控制)
*/
const publisher: Publisher = await session.declarePublisher(keyExpr, {
encoding: Encoding.TEXT_PLAIN, // 指定純文字 payload
priority: Priority.DATA, // 標準資料優先度
express: true // 開啟 express 傳輸(低延遲)
});
/*
* 步驟 3:發送訊息
* 資料會傳給所有符合的網路訂閱者
* 編碼可於單一訊息或用 publisher 預設值
*/
console.log(`Putting Data ('${keyExpr}': '${payload}')...`);
await publisher.put(payload, { encoding: Encoding.TEXT_PLAIN });
/*
* 步驟 4:宣告訂閱者(支援萬用字元範圍)
* 訂閱者會接收符合 key expr 模式的資料
* 例如 "demo/example/**" 匹配所有以 "demo/example/" 開頭主題
* handler 函式會處理每筆收到的資料(非同步)
*/
const subscriber: Subscriber = await session.declareSubscriber(
KeyExpr.autocanonize("demo/example/**"),
{
handler: (sample) => {
// 處理收到的每筆訊息
console.log(`Received ('${sample.keyExpr}': '${sample.payload}')`);
}
}
);
/*
* 步驟 5:讓應用保持運行
* 保留時間用於訊息交換並展示 pub/sub 溝通
* 實際應用場景中這會是主要業務邏輯
*/
await new Promise(resolve => setTimeout(resolve, 5000));
/*
* 步驟 6:資源清理
* 正確取消宣告 publisher/subscriber 並關閉 session
* 確保與 Zenoh 網路優雅斷線
*/
await subscriber.undeclare(); // 停止接收訊息
await publisher.undeclare(); // 停止發送
await session.close(); // 關閉 WebSocket 連線
}
main().catch(console.error);
欲了解更完整文件與專案範例,請參考 zenoh-ts repository。
# 為專案安裝 zenoh-ts 函式庫
npm install @eclipse-zenoh/zenoh-ts
# 或者使用 yarn
yarn add @eclipse-zenoh/zenoh-ts
zenoh-ts 需要一個 WebSocket 橋接,才能連線 Zenoh 網路:
# 方法 1:獨立橋接程序(開發推薦)
# 提供專用 WebSocket 端點給瀏覽器/Node.js
cargo run --bin zenoh-bridge-remote-api -- --ws-port 10000
# 方法 2:在 zenohd 啟用 remote-api plugin
# 多用於生產環境,可同時支援多個客戶端
zenohd -c zenoh-config.json5
{
// zenohd 配置 remote-api plugin
plugins: {
remote_api: {
// REST API 端點(選擇性)-支援 HTTP 查詢
rest: { port: 8080 },
// WebSocket 端點(必須)-提供給 zenoh-ts 連接
websocket: { port: 10000 }
}
}
}
/*
* 可查詢模式:服務端響應查詢
* 類似 RPC/REST 端點,可按需回覆資料
* 適合服務、資料庫、API實作
*/
const queryable = await session.declareQueryable(
KeyExpr.autocanonize("demo/service/**"),
{
handler: (query) => {
const response = `Response for ${query.keyExpr}`;
console.log(`Handling query: ${query.keyExpr}`);
query.reply(KeyExpr.autocanonize("demo/service/result"), response);
}
}
);
/*
* 查詢模式:客戶端發送查詢
* 傳送查詢並等待所有符合的服務端回覆
* 返回一個 promise,解析所有收到的回覆
*/
const replies = await session.get(
KeyExpr.autocanonize("demo/service/data"),
{ timeout: 5000 }
);
// 處理回覆
replies.forEach(reply => {
console.log(`Received reply: ${reply.payload}`);
});
透過整合 zenoh-pico 與 zenoh-ts,Zenoh 可確保從資源受限的邊緣設備到功能豐富的網頁介面之間,皆能高效、靈活與具擴展性地傳遞資料。
有興趣的讀者,歡迎深入這些專案、研究範例,發揮 Zenoh 統一資料層的極致潛能!