Robotics
ROS2 架構回顧[1]
specific rmw_adapter: 實作 rmw 的界面,目前主要有 rmw_fastrtps_cpp
rmw_connext_cpp
rmw_opensplice_cpp
三種,2018 年因為新的 DDS 實作 cyclonedds 出現而新增了 rmw_cyclonedds
rclcpp, rclpy 為 ROS2 提供給特定語言的函式庫,是以非同步的執行模型接收資料 (subscription),傳送資料也可以使用非同步的方式,如使用 timer 並註冊對應的 callback,為了實作非同步模型 ROS2 在 rclcpp, rclpy 中都定義了 Executor 的類別,而繼承 Executor 的類別更可以提供多執行緒處理 callback 的能力,或是 Mutual Exculsive,限制在多執行緒下單一時間只處理一個 callback。
ROS2 最常用的 spin()
函式則是使用單執行緒的 Executor,使用 add_node
將單一個 node 加入 Executor 中。
其中 get_next_executable()
會呼叫 get_next_ready_executable()
檢查目前有哪些 callback 可以執行,如果沒有則最後會呼叫 rcl_wait()
等待新的 callback 進入。
Response-Time Analysis of ROS 2 Processing Chains under Reservation-Based Scheduling[2] 這篇論文描述了 Executor 檢查 callback 的行為,以下為示意圖並附上相關程式碼:
從 rcl 層到 rmw_adapter 實作同步的設計模型,亦即等待訊號通知再去執行特定工作,這一類的操作必須使用 Waitset 和 GuardConition。
這兩個物件在 DDS 標準都有定義,多個 GuardCondition 可以連接 (attach) 到一個 Waitset 上,並等待任何一個 GuardCondition 通知,等待 Waitset 的程式就能被喚醒。每一個 DDS 實作可能會自己定義這兩個物件 (如 RTI Connext),因此 rmw 層提供一系列的物件和函式來包裝,讓 rmw 層以上獨立於任何一個 DDS 實作 (DDS agonstic)。
以下為 rmw 定義的 Waitset, GuardCondition:
成員 data
指向 DDS 實作自己定義的物件,要使用時再自行轉型 (只有在 rmw_adapter 中才會使用到 data
,rcl 不會操作這個資料)。
以下參考 rmw_fastrtps_cpp
的程式碼,fastrtps 的 DDS 實作中並沒有獨立定義這兩個物件,因此他直接使用 C++ 標準的 mutex 和 condition variable。
事實上不只 GuardCondition 可以連接到 Waitset 上,如 subscription, service, client, events 都是可以連接的物件,以下為 ROS2 中呼叫 rcl_wait()
最後會執行的函式,同樣取自 rmw_fastrtps_cpp
的實作 (rmw_wait()
-> __rmw_wait()
)
ROS2 message (在 package 一般稱為 rosidl,在使用時直接稱為 msg) 主要由兩個 package 實作,rosidl_generator 和 rosidl_typesupport。
rosidl_generator 產生 ROS2 應用程式所要操作的資料結構,如最簡單的 std_msgs/String.msg
rosidl_generator 便會產生有 data 資料成員的類別
rosidl_generator_c 中有 message_type_support_struct.h 標頭檔,定義了 rosidl_message_type_support_t
,告訴底層如何操作 ros message,這部份則由 typesupport 來實作。
另一個 package 叫 rosidl_typesupport 必須實作 ros message 和 DDS 資料結構的轉換,因為每個 DDS 實作的保存、操作資料 API 名稱都不一樣,因此對應的 rmw adapter 必須連帶提供 rosidl_typesupport 這個 package,以 rmw_fastrtps_cpp
來說就有提供 rosidl_typesupport_fastrtps_c 和 rosidl_typesupport_fastrtps_cpp,也是使用自動產生程式碼的方式。
typesupport 相關討論 http://docs.ros2.org/latest/developer_overview.html#internal-api-architecture-overview https://answers.ros.org/question/292170/rosidl-type-support-and-new-language/ https://groups.google.com/forum/#!topic/ros-sig-ng-ros/Nsd53ps3h6I
CycloneDDS 是如何實做 rmw_cyclonedds 的呢?首先先來看 rmw_cyclonedds_cpp 中的對於傳送資料的實作
commit 7cb3b38a21e14fbcc84aadcc460e4d812d0c7a7f (HEAD -> humble, tag: 1.3.4, origin/humble)
以上程式碼中 dds_write
為 CycloneDDS 中的函式,從這個函式開始就進入 CycloneDDS 的實作,dds_write
最終會呼叫 dds_write_impl_plain
將資料做序列化 (serialize) 並將其放入 CycloneDDS 的物件中。
至於要如何接收 DDS 資料,先參考別人的 call trace
https://github.com/ros2/rmw_cyclonedds/issues/3
可以看到 rmw_cyclonedds 使用了 rmw_cyclonedds_cpp::TypeSupport
這個物件來實作,這個物件實際上是一個 template。
注意到他有一個 method 叫做 deserializeROSmessage
其定義如下
DDSI-RTPS 是 DDS 通訊協定,每種 DDS 的實作都必須符合這套協議的規範,所以就算不同的 DDS 之間也能溝通,例如 fastrtps 的 publisher 傳送資料給 cyclonedds 的 subscriber。
以下為 RTPS 封包的結構,RTPS 通常使用 UDP/IP,而一個 RTPS Packet 可以分類成一個 RTPS Header 和一到數個 RTPS SubMessage。
Guid 是用來分辨一個 DDS Domain 上的某個 DDS Entity,如 DataReader, DataWriter 等,每個 DDS Entity 都有一個獨一無而的 Guid。