# micro-ROS 進階通訊篇 ## 一、前言與學習目標(Introduction & Objectives) ### 1. 通訊模型擴展說明 在嵌入式機器人與 ROS 生態系統中,單純的資料發布/訂閱(Publish/Subscribe)模型雖然強大,但已不足以涵蓋所有應用場景。舉例來說: - 跳過 “一直發送資料” 的持續流(Publish/Subscribe)模式,當你面對「一次性請求/回應」的場景(例如:主機問 MCU 讀取一次感測器數值),就會用到 Service。 - 當任務變為「長時間運行 + 多次回報進度」時(例如:機器手臂執行一組動作、導航器從 A 點移動到 B 點、或是感測器掃描整個房間),那麼 Action 模型就更合適。 - 同時,在資源受限、網路條件不佳(如 Wi-Fi、UDPless、串口連線)或多節點協作的情況下,僅靠預設通訊參數可能產生封包遺失、延遲異常或系統不穩。這就是 Quality of Service(QoS)設定的重要性。 本篇「進階通訊篇」的目的,就是在已熟悉 Publish/Subscribe 的基礎上,進一步補足 Service、Action 與 QoS 這三大進階維度。透過這樣,你將能在 ROS2/µROS 系統中: - 精準選用最合適的通訊模型 - 調整通訊品質,匹配實務環境(MCU、網路限制、多節點協作) - 了解 Host ↔ Agent ↔ MCU 三端架構如何實作並優化 ### 2. 本篇實作方向 為了使這些理論不只是紙上談兵,我們將採取以下路徑: 1. **概念解析**:簡明剖析 Topic、Service、Action 三大模型的差異與適用場景。 2. **參數升級**:深入 QoS 設定(Reliability、Durability、History、Depth 等)與 µROS/DDS/XRCE-DDS 相關限制。 3. **架構走查**:從通訊協定(DDS/Micro XRCE-DDS)與 Agent 角色講起,理解 Host/MCU 如何互動。 4. **實作骨架**:提供 Host(Python/C++)與 MCU(C、µROS)雙端程式碼範例,涵蓋 Publisher/Subscriber、Service 與 Action 的基本流程。 5. **實務挑戰**:分享 MCU 記憶體、頻率、網路不穩、Agent 負載等「真實世界坑」,並給出優化建議。 6. **延伸練習**:透過練習題或小案例(例如:/reset_device Service、感測器資料可靠傳送、簡單 Action 任務)幫你鞏固理解。 ### 3. 章節架構 以下為本篇章節總覽: | 章節 | 主題 | 重點內容 | |------|------|---------| | 二 | 通訊模式比較 | Topic vs Service vs Action | | 三 | QoS 深入探討 | Reliability, Durability, History 等 | | 四 | Transport 與協定 | DDS / XRCE-DDS / Agent 架構 | | 五 | 自訂訊息升級 | .msg / .srv / .action 實作 | | 六 | Host / MCU 雙端實作 | Python/C++ Host + C Client | | 七 | 延伸練習與實務應用 | 多節點、IoT、實體移植 | | 八 | 結語與未來方向 | 系統擴充、性能量測、實務問題 | ### 4. 學習目標 完成本篇後,你將能: - 辨識並選用最適合的通訊模型(Topic/Service/Action)於 µROS/ROS2 系統中。 - 理解並能設定 QoS 參數,使通訊在資源受限或不理想網路環境中依然穩定。 - 明白 Host ↔ Agent ↔ MCU 三層架構的運作機制,並能撰寫雙端程式碼。 - 使用自訂訊息型別(.msg/.srv/.action),並從概念到實作完成端到端範例。 --- ## 二、通訊模式比較(Communication Models) ### 1. Topic **定義與用途** 在 ROS 2/micro‑ROS 中,Topic 是節點之間用來進行「持續資料傳輸」的主要通道。當你有持續、頻繁、非同步的資料流(例如:感測器讀值、機器人狀態、影像資料)時,就會用 Topic。 **進階關鍵點** - Topic 為多對多(Many-to-Many)模型:多個 Publisher、或多個 Subscriber 都可以參與。 - Publisher 決定何時發送資料(非 Client 等待模式) - 在嵌入式/µROS 環境,除了基本發送/訂閱,還要考慮 QoS 設定(稍後章節)與 Agent/Transport 繞過問題。 **適用場景** - 感測器資料不斷輸出(如溫溼度、LiDAR、IMU) - 鏡頭影像串流 - 機器人定位/狀態更新 **不要用於** - 必須立即回應、等待結果的請求(如讀取檔案、觸發執行) → 這部分要用 Service。 - 長時間任務且需要進度回報、可能被取消 → 用 Action。 --- ### 2. Service(Request/Response) **定義與用途** Service 是 ROS2 中提供「同步請求-回應」模型的通訊方式。當一個節點(Client)向另一個節點(Server)發送請求後,會等待回應。適合「一次性」、「快速結束」的操作。 **結構與流程** - .srv 定義檔:包含 Request 與 Response 兩部分 - Client 發出 request → Server 處理 → 回傳 response - 含有同步等待行為(Client 無法繼續直到得到回覆) **適用場景** - 查詢機器人當前電量/健康狀態 - 啟動或停止某功能(如:清除地圖、重置設備) - 請求 MCU 執行「一次性任務」並回報結果 **比較簡單的限制** - 適合任務短、等待時間可接受的情況 - 如果任務長、或需要進度回報,會造成 Client 阻塞 → 改用 Action。 --- ### 3. Action(Goal/Feedback/Result) **定義與用途** Action 是用於「長時間任務」且可能需要「進度回報」或「可取消」的模型。Client 發送一個目標(Goal)後,Server 開始執行,並且能回傳 feedback、最後出結果(Result)。 **三段結構簡述** 1. **Goal**:欲達成的任務目標 2. **Feedback**:執行期間回報進度或狀態 3. **Result**:任務結束後的最終輸出(成功/失敗) **架構與行為特性** - 支援預設可取消(Cancelling)或中止(Aborting)任務 - 客戶端(Client)不需等待整個任務結束即可繼續其餘邏輯 - 背後實作通常結合 Topic + Service(例如:send_goal Service、feedback Topic、get_result Service) **適用場景** - 機器人從 A 點導航至 B 點(任務時間較長) - 圖像處理任務需掃描整張圖片並回報進度 - 多步驟機械臂動作序列,需要回報動作進度 **在 µROS/嵌入式中的考慮點** - 因資源受限(MCU 記憶體、RTOS 時間片、Agent 處理能力)使用較少,但理解仍重要 - 考題可能問結構或選擇理由,而非要求你從零實作 --- ### 4. 模式選擇指引(When to Use Which) | 模式 | 適用情境 | 典型範例 | |-----------|-------------------------------------------|-----------------------------------------------| | Topic | 資料流大、持續性強、不需回覆/請求 | 感測器數據、狀態廣播 | | Service | 一次性請求/快速回應、不需進度回報 | 查詢電量、觸發重置、讀取一次數值 | | Action | 長時間任務、需進度回報或可取消 | 導航、掃描任務、機械臂執行多步驟動作 | **記住這三句話** - 想「一直丟資料給大家」 → 用 Topic。 - 想「發一個請求、要一個答案」 → 用 Service。 - 想「下個任務給它、看它進度、結束或取消」 → 用 Action。 --- ### 5. 進階提示(對於 µROS/嵌入式環境特別注意) - 在 µROS 情境中,不要以為「Topic 一定最好」:因為架構跟 Host/Agent/Client(MCU)有額外通路和資源限制。 - Service 若處理時間過長,會阻塞 Client,因此對 MCU 端不要用太複雜的 Service。 - Action 雖然功能齊全,但 MCU 端若資源不足,用 Topic + 自訂同步邏輯有時會更「輕量」。 - 在章節後續會講的 QoS、Transport、Agent 設定,對模式選擇也有影響:Topic/Service/Action 在不同 QoS 下的行為可能差很多。 --- ## 三、QoS(Quality of Service)深入探討 ### 1. QoS Profile 組成 在 ROS 2/micro‑ROS 系統中,Quality of Service(QoS)設定允許你針對節點間通訊的「可靠性」「延遲」「資源使用」進行調整。 以下是主要政策(policies)與其說明: - **History(歷史記錄)**:定義 Publisher 存留舊訊息給訂閱者的策略。選項包括 `KEEP_LAST`(僅保留最近 N 筆)或 `KEEP_ALL`(保留所有/受資源限制)。 - **Depth(深度)**:配合 `KEEP_LAST` 使用,定義隊列(緩衝)大小。例如 depth = 5 表示保留最近五筆訊息。 - **Reliability(可靠性)**:定義資料送達保障程度。`RELIABLE` 保證送達(有重傳機制),`BEST_EFFORT` 儘力送達但可能遺失。 - **Durability(耐久性)**:定義新訂閱者加入後是否可看到舊訊息。`VOLATILE`:不保留、只對當下訂閱者;`TRANSIENT_LOCAL`:保留最近一筆/幾筆資料供後加入者讀。 - **Deadline**:定義預期兩次資料發布之間的最大時間間隔,若超過會觸發事件。 - **Lifespan**:定義資料被視為「過期/無效」之前的最大存活時間。 - **Liveliness + LeaseDuration**:定義節點或出版者是否維護「活著」狀態的機制。 > **提醒**:只要 QoS 設定不相容(Publisher/Subscriber 或 Client/Server 之間),即便訊息型別、Topic 名稱都對,通訊仍可能失敗。 --- ### 2. micro-ROS 與 QoS 在嵌入式中的限制與考量 在嵌入式/微控制器環境中使用 micro-ROS,雖然 QoS 概念與 ROS2 相同,但**實務限制**必須被納入設計。 - MCU 的記憶體、Flash、CPU 通常比 Host 少很多,因此若設定 `KEEP_ALL` 或 depth 很大,可能導致記憶體不足甚至系統崩潰。 - 在 micro-ROS 所用的底層通訊協定 Micro XRCE‑DDS 中,即便你選 `RELIABLE`,底層仍可能因為代理(Agent)+UDP/串口傳輸造成遺失或延遲。 - 在無線網路、RS-232、或頻率很高的場景,「少許資料遺失比整個系統卡住更好」的情況不少。這時候 `BEST_EFFORT` + 小隊列可能比 `RELIABLE` + 大隊列還更實際。 - 設定 QoS 時,不只是程式碼中填值那麼簡單:你還要考慮通道(Transport)、Agent 負載、MCU 資源、頻率與資料量這些變數。 --- ### 3. 各通訊模型與 QoS 的搭配 這裡快速比對「Topic/Service/Action」三種通訊模型在實務上常用的 QoS 設定建議。你在考場看到問 QoS +模型時就能秒答。 | 通訊模型 | 建議 QoS 設定 | 為什麼 | |---------------|--------------------------------------------------------|------------------------------------------------------| | **Topic** | 感測器資料流:`BEST_EFFORT` + 小隊列(depth 5-10)<br>關鍵狀態/命令:`RELIABLE` + 適中隊列(depth 10-20) | 資料量大、頻率高、偶有遺失可忍;狀態/命令不能遺失 | | **Service** | 通常 `RELIABLE` + 預設隊列 | 是同步請求-回應模式,不能丟失結果 | | **Action** | Goal: 快速觸發 <br>Feedback: 可用 `BEST_EFFORT` <br>Result: 使用 `RELIABLE` | 長任務需回報進度可容錯,結果不能遺失 | --- ### 4. 實作範例(Host C++ + MCU C++) **Host(C++)範例:** ```cpp #include <rclcpp/rclcpp.hpp> #include <my_pkg/msg/SensorData.hpp> int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = rclcpp::Node::make_shared("sensor_publisher"); auto qos_profile = rclcpp::QoS(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_default)) .reliable() .keep_last(5); auto publisher = node->create_publisher<my_pkg::msg::SensorData>("/sensor/data", qos_profile); rclcpp::Rate loop_rate(10); // 10 Hz while (rclcpp::ok()) { auto message = my_pkg::msg::SensorData(); // 填充 message 的資料欄位…… publisher->publish(message); rclcpp::spin_some(node); loop_rate.sleep(); } rclcpp::shutdown(); return 0; } ```` **MCU(C++)範例:** ```cpp #include <rclcpp/rclcpp.hpp> #include <my_pkg/msg/SensorData.hpp> #include "rclc/rclc.h" // 假設使用 rclc 或對應 MCU C++ API int main() { // MCU 通常建立 rclc node, executor 等 rcl_node_t * node = rclc_node_init_default(...); rcl_publisher_t publisher; rcl_publisher_options_t opts = rcl_publisher_get_default_options(); opts.qos.reliability = RMW_QOS_POLICY_RELIABILITY_RELIABLE; opts.qos.history = RMW_QOS_POLICY_HISTORY_KEEP_LAST; opts.qos.depth = 5; rcl_publisher_init( &publisher, node, ROSIDL_GET_MSG_TYPE_SUPPORT(my_pkg, msg, SensorData), "/sensor/data", &opts ); while (rcl_ok()) { my_pkg__msg__SensorData msg = my_pkg__msg__SensorData__create(); // 填充資料 rcl_publish(&publisher, &msg, nullptr); // MCU 需考慮延遲、記憶體、回呼處理 } return 0; } ``` > 補充說明:在 MCU 端你還要確保 static memory、stack size、回呼函式效能、串口/通道穩定這些條件。 --- ### 5. 測試項目與預期結果 做這些實驗可幫你更理解 QoS 設定如何影響系統。也很適合在考前練一遍。 | 項目 | 設定 | 預期結果 | | ------------------------- | ------------------- | ---------------------------------------------- | | Reliability = RELIABLE | depth = 10 | 資料可靠送達但延遲可能稍高 | | Reliability = BEST_EFFORT | depth = 1 | 延遲低、資源用少,但可能頻繁遺失封包 | | Depth = 1 vs Depth = 10 | History = KEEP_LAST | 小 depth:舊訊息快速被覆蓋 <br>大 depth:保留較多訊息但記憶體/延遲負擔更高 | --- ### 6. 常見坑與除錯建議 * Publisher 設 `RELIABLE` 而 Subscriber 設 `BEST_EFFORT` → **不相容**,可能無法收到資料。 * 設定過深隊列在 MCU 上直接可能導致記憶體不足或系統卡住。 * 使用 `RELIABLE` 但頻率太高、沒有適當時間片在 MCU 上,很容易造成堆積延遲。 * 在無線/低速通道中硬用 `RELIABLE` 可能比用 `BEST_EFFORT`還差,因為重傳+代理負載加大。 * 忘了 Durability 設定:若系統需要「新訂閱者加入後見到最後狀態」,必須用 `TRANSIENT_LOCAL`。 --- ## 四、Transport 與協定(Transport & Protocol) ### 1. Data Distribution Service (DDS) 架構概述 在 ROS 2 中,DDS 是底層通訊的核心中介。DDS 定義了資料中心發佈/訂閱模型(Publish-Subscribe),同時支援 QoS 設定、探索機制(discovery)、跨平台互通。:contentReference[oaicite:2]{index=2} 簡要說明: - Publisher/Subscriber 模型對應到 DDS 中的 DataWriter/DataReader。 - Nodes(節點)成為 DDS Participants,加入 Global Data Space。 - 透過 RTPS 或其他底層協定進行封包傳輸。 ### 2. DDS‑XRCE(µROS)架構與通訊鏈路 當 MCU/微控制器要加入 ROS 2 生態(例如 micro‑ROS)時,不直接用 DDS,而是用 DDS-XRCE(DDS for eXtremely Resource Constrained Environments)。:contentReference[oaicite:5]{index=5} 特色如下: - 採 Client-Server 架構:MCU 端為 XRCE Client,主機端為 XRCE Agent,Agent 代表 Client 存取 DDS Global Data Space。:contentReference[oaicite:6]{index=6} - 支援多傳輸通道:UDP、TCP、Serial 等。:contentReference[oaicite:7]{index=7} - 設計為低資源消耗:典型記憶體為 < 75 KB Flash + ~3 KB RAM。:contentReference[oaicite:8]{index=8} ### 3. Agent/Client/Transport 三層資料流圖與解說 在 micro-ROS 系統中,典型資料流程如下: - MCU(XRCE Client)透過 Transport(例如:UART、UDP)將資料傳送給 XRCE Agent。 - Agent 接收 XRCE 封包後,轉成 DDS 訊息並發佈至 ROS2 系統內的 Subscriber。 - 反方向亦然:ROS2 系統中的 Publisher 資料可經 Agent 傳至 MCU 端 Subscriber。 **教學提醒**:在考場可能會出「Agent 是什麼角色?」「為什麼 MCU 不能直接用 DDS?」「Transport 層有哪些限制?」這類題。 ### 4. 傳輸通道選擇(Transport)與實務考量 在 micro-ROS/嵌入式環境中,「傳輸」比你在 PC 開發時要複雜。你必須考慮通道、頻率、延遲、資源限制。部分常見通道與考量: - **Serial (UART)**:硬體需求低、能在 MCU 上使用,但頻率低、錯誤率高、可能因通道瓶頸導致資料堆積。 - **UDP**:延遲較低、適合大量資料流,但不保證可靠性。 - **TCP**:提供可靠性,但手續繁複、延遲可能增加、資源消耗較大。 - **Shared Memory/RPMsg/OpenAMP**(當 MCU 與 Host 在同一 SoC 上):延遲最小但硬體依賴強。 **考試常見題**: > 若 MCU 選用 UART 傳資料,但資料頻率為 1 ms/筆、payload 500 bytes,哪裡最可能出問題? > → 通道頻寬、記憶體積壓、Agent/MCU 處理能力不足。 ### 5. 系統整合與效能瓶頸 實務中你會遇到這些坑,考場也會問: - MCU → Agent → DDS 的轉換會產生額外延遲:Agent 必須解封包、轉格式、處理錯誤。 - 若有大量 Topic、高頻率、大 payload 資料,MCU 可能因記憶體或 CPU 不夠而掉封包。 - 傳輸通道如無線/低速環境時,要特別注意 QoS+Transport 的配合。 - Agent 需與 Host 網路同步:Topic/Participant 數量多可能造成負載高、處理延遲大。 在考題中你可能被問:「系統延遲比預期高,可能原因是什麼?」你要從 Transport 層分析。 ### 6. 實作範例(C++) 以下是簡化的 C++ 範例,描述 Agent 建立 UDP 傳輸與 MCU 端通訊流程(教學示範,實務中可能用 C 或特定 API): ```cpp // Host (Agent) 側:初始化 UDP Transport #include <MicroXrceAgent.hpp> #include <FastDDS/Publisher.hpp> int main() { XRCEAgent agent; agent.init_transport_udp("0.0.0.0", 7400); while (agent.running()) { XRCEMessage msg; if (agent.receive(msg)) { ddsPublisher.publish(msg.to_dds()); } } return 0; } ``` ```cpp // MCU (Client) 側:使用 XRCE Client 傳輸 SensorData #include <MicroXrceClient.hpp> #include <my_pkg/msg/SensorData.hpp> int main() { MicroXrceClient client; client.init_transport_udp("192.168.1.100", 7400); my_pkg::msg::SensorData data{1, 25.0f, 60.0f}; while (true) { client.publish("/sensor/data", data); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } return 0; } ``` > 補充說明:實務中 Client 端還要設定 Token、Stream、連線保護、錯誤處理、記憶體池管理等。 --- ## 五、自訂訊息升級(Custom Message Upgrade) ### 1. `.msg / .srv / .action` 檔案格式說明 在 ROS 2/micro‑ROS 系統中,訊息、服務、動作的介面定義是透過三種檔案格式完成的: - `.msg` 檔案:用於定義一條資料流(message)。 - `.srv` 檔案:用於定義一次性請求回應的資料型別(service:Request/Response)。 - `.action` 檔案:用於定義一個長時任務(action),內含 Goal、Feedback、Result 三段資料。 這些檔案的格式都有固定語法,如 `.srv` 檔案中,Request 與 Response 之間用 `---` 分隔。 --- ### 2. 自訂 `.msg` 範例:`SensorData.msg` 假設你要在 MCU /感測器系統上傳送自訂資料包,以下是一個範例: ```text int32 id float32 temperature float32 humidity string source ```` 這表示: * `id`:整數型別感測器編號 * `temperature`:單精度浮點數溫度值 * `humidity`:單精度浮點數濕度值 * `source`:字串,描述資料來源或節點名稱 撰寫自訂 `.msg` 的步驟包括: 1. 在 interface package 下創建 `msg/` 資料夾。 2. 新建 `SensorData.msg` 檔案並填寫如上內容。 3. 在 `CMakeLists.txt` 及 `package.xml` 中加入生成指令與依賴設定。 --- ### 3. 建置步驟(修改 `package.xml`/`CMakeLists.txt`) 為了讓你的自訂介面檔被編譯並可被其他節點使用(C++/C/Python),你必須做以下設定: * 在 `package.xml` 中加入: ```xml <buildtool_depend>rosidl_default_generators</buildtool_depend> <exec_depend>rosidl_default_runtime</exec_depend> <member_of_group>rosidl_interface_packages</member_of_group> ``` 並宣告對其他用到的訊息包(如 `geometry_msgs`)的 `<depend>`。 ([The Robotics Back-End][2]) * 在 `CMakeLists.txt` 中加入: ```cmake find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/SensorData.msg" "srv/…" "action/…" DEPENDENCIES geometry_msgs ) ``` * build workspace: ````bash colcon build --packages-select <your_interfaces_package> source install/setup.bash ``` :contentReference[oaicite:8]{index=8} ```` --- ### 4. Python/C++ 節點使用自訂訊息範例 假設你已定義 `SensorData.msg` 並 build 成功,以下是在 Host(C++)端使用範例: ```cpp #include <rclcpp/rclcpp.hpp> #include <my_interfaces_pkg/msg/sensor_data.hpp> void callback(const my_interfaces_pkg::msg::SensorData::SharedPtr msg) { RCLCPP_INFO( rclcpp::get_logger("sub"), "Received id=%d temperature=%.2f humidity=%.2f source=%s", msg->id, msg->temperature, msg->humidity, msg->source.c_str() ); } int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = rclcpp::Node::make_shared("sensor_subscriber"); auto sub = node->create_subscription<my_interfaces_pkg::msg::SensorData>( "/sensor/data", 10, callback ); rclcpp::spin(node); rclcpp::shutdown(); return 0; } ``` 使用自訂服務 `.srv` 或動作 `.action` 的流程與此類似,只是你換成 `srv`/`action` 的 API,並注意 Goal/Feedback/Result 的架構。 --- ### 5. 延伸:`.srv`/`.action` 用法簡述 * **.srv** 範例: ```text int32 id float32 value --- bool success ``` 表示:Client 提交 `id` 與 `value`,Server 回傳 `success`。 * **.action** 範例: ```text int32 goal_id --- bool result_ok --- float32 progress ``` 表示:Goal 傳入 `goal_id`,Result 回傳 `result_ok`,Feedback 回傳 `progress`。 你就能在動作執行時回報進度。 --- ## 六、Host/MCU 雙端實作(Host & MCU Dual-end Implementation) ### 1. Host(C++)節點架構說明 在 ROS 2(C++)端,我們典型使用 rclcpp 建立節點、Publisher/Subscriber、Service Client 或 Action Client。 - 節點初始化:`rclcpp::init(argc, argv)` → 建立 `rclcpp::Node::make_shared("node_name")` - 建立通訊物件:publisher, subscription, client or action - 處理迴圈:使用 `rclcpp::spin(node)` 或者 `rclcpp::spin_some(node)` + `rclcpp::Rate`進行定周期操作 範例流程:Publisher → 發送自訂訊息 → Subscriber 端處理 → MCU 端接收或回應。 ### 2. MCU(C/C++)節點架構說明 在 micro‑ROS(MCU)端,因資源受限,我們會選用 rclc (或微控制器支援的 ROS 客户端库)並搭配 XRCE-DDS 通訊。關鍵點: - 靜態記憶體分配:避免動態 new/delete,使用 static buffer 或 pool。 - 建立節點:`rclc_node_init_default()` 或對應 C++ 封裝。 - 建立 Publisher/Subscriber/Service/Action:使用 `rcl_publisher_init()`、`rcl_subscription_init()` 等低階 API。 - 主迴圈注意:不允許阻塞過久、必須處理 Agent 連線、監控記憶體與時間片。 ### 3. 建立 Publisher/Subscriber/Service/Action 程式碼骨架 以下示範 C++ 語法骨架(Host 端)及 MCU 端(C++ 或 rclc)骨架,作為學習範本。 **Host(C++)Publisher 範例**: ```cpp #include <rclcpp/rclcpp.hpp> #include <my_interfaces_pkg/msg/sensor_data.hpp> int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = rclcpp::Node::make_shared("host_sensor_publisher"); auto publisher = node->create_publisher<my_interfaces_pkg::msg::SensorData>("/sensor/data", 10); rclcpp::Rate rate(10); // 10 Hz while (rclcpp::ok()) { auto msg = my_interfaces_pkg::msg::SensorData(); // 填充 msg.id, msg.temperature, msg.humidity, msg.source publisher->publish(msg); rclcpp::spin_some(node); rate.sleep(); } rclcpp::shutdown(); return 0; } ```` **MCU(C++/rclc)Publisher 範例**: ```cpp #include <rclc/rclc.h> #include <my_interfaces_pkg/msg/sensor_data.h> int main() { rcl_allocator_t allocator = rcl_get_default_allocator(); rclc_support_t support; rclc_support_init(&support, 0, nullptr, &allocator); rcl_node_t node; rclc_node_init_default(&node, "mcu_sensor_node", "", &support); rcl_publisher_t publisher; rcl_publisher_options_t opts = rcl_publisher_get_default_options(); opts.qos.reliability = RMW_QOS_POLICY_RELIABILITY_RELIABLE; opts.qos.history = RMW_QOS_POLICY_HISTORY_KEEP_LAST; opts.qos.depth = 5; rcl_publisher_init(&publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(my_interfaces_pkg, msg, SensorData), "/sensor/data", &opts); while (rcl_ok()) { my_interfaces_pkg__msg__SensorData msg = my_interfaces_pkg__msg__SensorData__create(); // 填充 msg fields rcl_publish(&publisher, &msg, nullptr); rclc_executor_spin_some(&support.executor, RCL_MS_TO_NS(100)); } return 0; } ``` ### 4. 記憶體、資源與效能限制最佳化 在 MCU 端你必須特別注意以下限制: * **記憶體池與 stack 大小**:訂閱/回呼數過多、隊列過大容易耗盡 RAM。 * **Publisher/Subscriber 頻率控制**:頻率太高 + 資料量過大 → Agent / MCU 端壓力大、可能遺失訊息或掉線。 * **串口/通道穩定性**:若使用 Serial/WiFi,連線不穩或重連機制不佳會導致通訊中斷。 * **Agent 延遲與 MCU 處理週期**:Host ⇄ Agent ⇄ MCU 的延遲必納入設計(尤其 QoS 選擇、頻率設計、隊列 depth 設定)。 ### 5. 移植注意事項:Linux 模擬 vs 實體開發(ESP32/STM32) * **Linux 模擬**:使用 ROS2 環境模擬 MCU 端,可快速調試 Topic、Service、Action,但無 RTOS/硬體限制,表現比真實 MCU 理想。 * **實體開發**:比如 ESP32 或 STM32,需考慮實體 MCU 規格、電源、通道、藍牙/WiFi、冷啟動、重連機制、代理負載、記憶體與 RTOS 時間片。 * **建議流程**:先在模擬環境完成邏輯,再移植至實體,並逐步測試 MCU 資源使用、連線穩定、延遲狀況。 ### 6. 小練習 請你在 Host 端建立一個 Publisher 傳送 SensorData,自訂 .msg 檔案;MCU 端建一個 Subscriber 接收資料並回傳 ack 訊息(透過 Service 模型)。 * 測試 Topic 頻率從 10 Hz 提升到 100 Hz,觀察 MCU 端是否遺失訊息。 * 調整 QoS 設定(如 Reliable vs Best Effort),觀察 效果。 * 測試断線重連:MCU 重啟後訂閱 Topic 是否能恢復。 --- ## 七、延伸練習與實務應用(Extended Exercises & Practical Applications) ### 1. 多節點協作示例:感測器網絡與機器人集群 在 micro-ROS/ROS 2 系統中,當你不只是單台 MCU+Host,而是多個 MCU 節點、Host 節點、雲端伺服器協作時,下列為你可實作的架構: - 多個 MCU 節點(如 ESP32、STM32)分別連接不同感測器、執行不同控制任務; - 各 MCU 透過 Micro XRCE-DDS Agent 與 ROS 2 網絡連接,將資料匯聚到 Host/伺服器; - Host 節點中的 ROS 2 節點負責整合資料、策略決策或發布指令給機器人/裝置; - 將部分節點佈署在雲端/邊緣設備,用於資料儲存、分析、儀表板展示。 你可以設計練習題: 1. 建立 3 台 MCU 節點,每台分別發布不同資料(如溫度、濕度、加速度)至 ROS 2 網絡。 2. Host 節點訂閱這些資料,進行合併與簡單邏輯(如:若溫度>30 °C 且加速度>x,則發出警報指令)。 3. 將指令以 Service / Action 形式回送至 MCU 節點,讓 MCU 執行動作(如:啟動風扇、儲存資料、顯示警示)。 4. 模擬通訊異常(如:斷線、頻率降低、資料遺失)並觀察 QoS/傳輸選擇對系統影響。 ### 2. IoT 與雲端整合應用 嵌入式系統+ ROS 2 不再侷限於機器人,亦可擴展至 IoT 與雲端架構。舉例: - 使用 MQTT / HTTP 與 ROS 2 整合,將 MCU 資料送至雲端平台;例如 EMQX + micro-ROS 整合應用。 :contentReference[oaicite:3]{index=3} - 在 智慧倉儲、智慧農業、智慧工廠中,部署多個 MCU 感測器+機器人,透過 ROS 2 及 micro-ROS 實現分散式控制與協作。 :contentReference[oaicite:4]{index=4} - 將 ROS 2 節點、MCU 節點及雲端服務整合,在雲端呈現儀表板、資料分析、遠端監控。 練習題: - 將 MCU 端資料通過 micro-ROS Agent → ROS 2 節點 → MQTT Broker,再送至雲端資料庫。 - 設計雲端儀表板,監控多個節點的狀態(如:聯網狀態、資料頻率、漏包率),並在異常時發出通知。 ### 3. 實體移植與資源限制評估 當你將系統從實驗環境移植至實體設備(如 STM32 + FreeRTOS、ESP32、樹莓派+MCU),需特別注意資源與環境限制: - 硬體通道/網路品質:WiFi、串口、CAN 總線、4G/5G 模組; - MCU 記憶體、Flash、CPU 頻率、RTOS/任務設計; - Agent 負載、節點數量、Topic 數量、頻率、資料大小; - 系統部署佈景:邊緣-雲架構、節點重啟/恢復機制、時延監控。 練習題: 1. 將 MCU 執行頻率從 10 Hz 提升至 100 Hz 並測量漏包/延遲。 2. 在 WiFi 不穩條件下,改用 串口 / CAN 鏈路 + depth = 1、BEST_EFFORT QoS,觀察通訊品質差異。 3. 部署 ROS 2 節點於邊緣設備,測試 Agent 重啟/ MCU 重啟後系統恢復流程。 ### 4. 練習題庫與模擬考題 | 題號 | 題目 | 要點 | |------|------|------| | Q7-1 | 當 MCU 節點需要定期傳送資料給 Host/雲端,但通道時常不穩定,你會選哪種 QoS + 通訊模型? | 減少漏包/控制延遲/資源限制 | | Q7-2 | 在多節點協作系統中,若 Agent 負載過高造成延遲,你可以從哪三個維度著手優化? | 節點數、頻率、傳輸通道 | | Q7-3 | 將 ROS 2 系統擴展至雲端整合時,資料安全/鑑權/資料一致性需考慮哪些? | Protocol 選擇、Broker 結構、資料同步 | ### 5. 實務應用案例展示 - 在「智慧農業」專案中,多台 MCU 感測器部署於田間,使用 micro-ROS 透過 UDP 傳送資料至 Host,Host 再將資料上傳至雲端分析,並根據分析結果觸發灌溉裝置。 - 在「機器人倉儲」系統中,機器人與 MCU 感測單元協作,透過 ROS 2 控制路徑規劃,同時 MCU 回報感測器資料並接受 Action 指令執行收/放貨流程。