iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Rust

30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊系列 第 20

Day 20: 上至網頁前端下至嵌入式裝置:探索zenoh-pico & zenoh-ts

  • 分享至 

  • xImage
  •  

上至網頁前端下至嵌入式裝置:探索zenoh-pico & zenoh-ts

Eclipse Zenoh 在 Rust 和 JVM 環境中提供了強大的核心實作,還支援其他的綁定,以滿足多樣的應用需求。今天來介紹兩個值得注意的專案,zenoh-picozenoh-ts,將 Zenoh 的觸角延伸到嵌入式設備和網路應用。

1. zenoh-pico:適用於受限裝置的原生 C 函式庫

zenoh-pico 提供了一個輕量、
原生的 C 實作,專為嵌入式系統、
微控制器和即時作業系統(RTOS)所優化。
zenoh-pico 與主要的 Rust Zenoh stack 相容,為需要 Zenoh 能力的低資源平台搭建了橋梁。

主要特性

  • 為 IoT 裝置與感測器設計的極小足跡函式庫。
  • 支援 UDP、TCP、IPv4/IPv6、6LoWPAN 以及 Zephyr、Arduino、ESP-IDF、Raspberry Pi Pico 等多平台通用的傳輸層。
  • 可進行點對點多播通訊,不需中心代理。
  • 內建範例與 PlatformIO 整合,方便快速開發。

入門範例(嵌入式系統)

#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 提供多平台支援與專屬建構配方:

平台支援矩陣

  • Unix/Linux:完整支援 UDP/TCP、IPv4/IPv6
  • Windows:桌面應用完全相容
  • Zephyr RTOS:專為 IoT 優化,支援 6LoWPAN
  • Arduino:主流微控制器平台,支援 WiFi/Bluetooth
  • ESP-IDF:Espressif ESP32/ESP8266 平台支援
  • MbedOS:ARM 專為 IoT 設計的作業系統
  • Raspberry Pi Pico:支援 WiFi(W 版)與 USB CDC
  • FreeRTOS-Plus-TCP:即時作業系統支援
  • Emscripten:編譯為 WebAssembly,支援瀏覽器

建構系統整合

# 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>

2. zenoh-ts:TypeScript / JavaScript API

zenoh-ts 讓網頁開發者可透過 TypeScript 使用 Zenoh 的 pub/sub、store 及 query 機能,透過 WebSocket 與 Zenoh router 連線,支援瀏覽器與 Node.js 類環境。

亮點

  • 瀏覽器端可用 WebSocket 連結 Zenoh 遠端 API 插件
  • 支援慣用的非同步程式設計(回呼、通道)
  • 內建豐富範例,涵蓋指令列、瀏覽器聊天室等
  • 可從 npm 直接安裝,方便整合

入門範例(TypeScript)

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

安裝與設定

NPM 安裝

# 為專案安裝 zenoh-ts 函式庫
npm install @eclipse-zenoh/zenoh-ts

# 或者使用 yarn
yarn add @eclipse-zenoh/zenoh-ts

設定 zenoh-bridge-remote-api

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

zenoh-config.json5 範例

{
  // zenohd 配置 remote-api plugin
  plugins: {
    remote_api: {
      // REST API 端點(選擇性)-支援 HTTP 查詢
      rest: { port: 8080 },
      // WebSocket 端點(必須)-提供給 zenoh-ts 連接
      websocket: { port: 10000 }
    }
  }
}

進階用法範例

Query/Queryable 模式

/*
 * 可查詢模式:服務端響應查詢
 * 類似 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-picozenoh-ts,Zenoh 可確保從資源受限的邊緣設備到功能豐富的網頁介面之間,皆能高效、靈活與具擴展性地傳遞資料。

有興趣的讀者,歡迎深入這些專案、研究範例,發揮 Zenoh 統一資料層的極致潛能!


上一篇
Day 19: Zenoh Kotlin:結合 Rust 的效能與 Kotlin 的優雅
下一篇
Day 21: Zenoh 無所不在:回顧 Zenoh 如何從 Rust 出發,實踐跨語言綁定 (Binding)
系列文
30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言