ROS——通訊(一)
ROS中的通訊方式有四種,主題、服務、引數伺服器、動作庫。
Node
在ROS的世界裡,最小的程序單元就是節點(node)。一個軟體包裡可以有多個可執行檔案,可執行檔案在執行之後就成了一個程序(process),這個程序在ROS中就叫做節點。 從程式角度來說,node就是一個可執行檔案(通常為C++編譯生成的可執行檔案、Python指令碼)被執行,載入到了記憶體之中;從功能角度來說,通常一個node負責者機器人的某一個單獨的功能。由於機器人的功能模組非常複雜,我們往往不會把所有功能都集中到一個node上,而會採用分散式的方式,把雞蛋放到不同的籃子裡。例如有一個node來控制底盤輪子的運動,有一個node驅動攝像頭獲取影象,有一個node驅動鐳射雷達,有一個node根據感測器資訊進行路徑規劃……這樣做可以降低程式發生崩潰的可能性,試想一下如果把所有功能都寫到一個程式中,模組間的通訊、異常處理將會很麻煩。
我們在1.4節打開了小海龜的運動程式和鍵盤控制程式,在1.5節同樣啟動了鍵盤運動程式,這每一個程式便是一個node。ROS系統中不同功能模組之間的通訊,也就是節點間的通訊。我們可以把鍵盤控制替換為其他控制方式,而小海龜運動程式、機器人模擬程式則不用變化。這樣就是一種模組化分工的思想。
3.1.2 Master
由於機器人的元器件很多,功能龐大,因此實際執行時往往會執行眾多的node,負責感知世界、控制運動、決策和計算等功能。那麼如何合理的進行調配、管理這些node?這就要利用ROS提供給我們的節點管理器master, master在整個網路通訊架構裡相當於管理中心,管理著各個node。node首先在master處進行註冊,之後master會將該node納入整個ROS程式中。node之間的通訊也是先由master進行“牽線”,才能兩兩的進行點對點通訊。當ROS程式啟動時,第一步首先啟動master,由節點管理器處理依次啟動node。
3.1.3 啟動master和node
當我們要啟動ROS時,首先輸入命令:
$ roscore
此時ROS master啟動,同時啟動的還有rosout
和parameter server
,其中rosout
是負責日誌輸出的一個節點,其作用是告知使用者當前系統的狀態,包括輸出系統的error、warning等等,並且將log記錄於日誌檔案中,parameter server
即是引數伺服器,它並不是一個node,而是儲存引數配置的一個伺服器,後文我們會單獨介紹。每一次我們執行ROS的節點前,都需要把master啟動起來,這樣才能夠讓節點啟動和註冊。
master之後,節點管理器就開始按照系統的安排協調進行啟動具體的節點。節點就是一個程序,只不過在ROS中它被賦予了專用的名字裡——node。在第二章我們介紹了ROS的檔案系統,我們知道一個package中存放著可執行檔案,可執行檔案是靜態的,當系統執行這些可執行檔案,將這些檔案載入到記憶體中,它就成為了動態的node。具體啟動node的語句是:
$ rosrun pkg_name node_name
通常我們執行ROS,就是按照這樣的順序啟動,有時候節點太多,我們會選擇用launch檔案來啟動,下一小節會有介紹。 Master、Node之間以及Node之間的關係如下圖所示:
3.1.3 rosrun和rosnode命令
rosrun命令的詳細用法如下:
$ rosrun [--prefix cmd] [--debug] pkg_name node_name [ARGS]
rosrun將會尋找PACKAGE下的名為EXECUTABLE的可執行程式,將可選引數ARGS傳入。 例如在GDB下執行ros程式:
$ rosrun --prefix 'gdb -ex run --args' pkg_name node_name
rosnode命令的詳細作用列表如下:
rosnode命令 | 作用 |
---|---|
rosnode list |
列出當前執行的node資訊 |
rosnode info node_name |
顯示出node的詳細資訊 |
rosnode kill node_name |
結束某個node |
rosnode ping |
測試連線節點 |
rosnode machine |
列出在特定機器或列表機器上執行的節點 |
rosnode cleanup |
清除不可到達節點的註冊資訊 |
以上命令中常用的為前三個,在開發除錯時經常會需要檢視當前node以及node資訊,所以請記住這些常用命令。如果你想不起來,也可以通過rosnode help
來檢視rosnode
命令的用法。
launch檔案
3.2.1 簡介
機器人是一個系統工程,通常一個機器人執行操作時要開啟很多個node,對於一個複雜的機器人的啟動操作應該怎麼做呢?當然,我們並不需要每個節點依次進行rosrun,ROS為我們提供了一個命令能一次性啟動master和多個node。該命令是:
$ roslaunch pkg_name file_name.launch
roslaunch命令首先會自動進行檢測系統的roscore有沒有執行,也即是確認節點管理器是否在執行狀態中,如果master沒有啟動,那麼roslaunch就會首先啟動master,然後再按照launch的規則執行。launch檔案裡已經配置好了啟動的規則。 所以roslaunch
就像是一個啟動工具,能夠一次性把多個節點按照我們預先的配置啟動起來,減少我們在終端中一條條輸入指令的麻煩。
3.2.2 寫法與格式
launch檔案同樣也遵循著xml格式規範,是一種標籤文字,它的格式包括以下標籤:
<launch> <!--根標籤-->
<node> <!--需要啟動的node及其引數-->
<include> <!--包含其他launch-->
<machine> <!--指定執行的機器-->
<env-loader> <!--設定環境變數-->
<param> <!--定義引數到引數伺服器-->
<rosparam> <!--啟動yaml檔案引數到引數伺服器-->
<arg> <!--定義變數-->
<remap> <!--設定引數對映-->
<group> <!--設定名稱空間-->
</launch> <!--根標籤-->
參考連結:http://wiki.ros.org/roslaunch/XML
3.2.3 示例
launch檔案的寫法和格式看起來內容比較複雜,我們先來介紹一個最簡單的例子如下:
<launch>
<node name="talker" pkg="rospy_tutorials" type="talker" />
</launch>
這是官網給出的一個最小的例子,文字中的資訊是,它啟動了一個單獨的節點talker
,該節點是包rospy_tutorials
軟體包中的節點。
然而實際中的launch檔案要複雜很多,我們以Ros-Academy-for-Beginners
中的robot_sim_demo
為例:
<launch>
<!--arg是launch標籤中的變數宣告,arg的name為變數名,default或者value為值-->
<arg name="robot" default="xbot2"/>
<arg name="debug" default="false"/>
<arg name="gui" default="true"/>
<arg name="headless" default="false"/>
<!-- Start Gazebo with a blank world -->
<include file="$(find gazebo_ros)/launch/empty_world.launch"> <!--include用來巢狀模擬場景的launch檔案-->
<arg name="world_name" value="$(find robot_sim_demo)/worlds/ROS-Academy.world"/>
<arg name="debug" value="$(arg debug)" />
<arg name="gui" value="$(arg gui)" />
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="headless" value="$(arg headless)"/>
</include>
<!-- Oh, you wanted a robot? --> <!--嵌套了機器人的launch檔案-->
<include file="$(find robot_sim_demo)/launch/include/$(arg robot).launch.xml" />
<!--如果你想連同RViz一起啟動,可以按照以下方式加入RViz這個node-->
<!--node name="rviz" pkg="rviz" type="rviz" args="-d $(find robot_sim_demo)/urdf_gazebo.rviz" /-->
</launch>
這個launch檔案相比上一個簡單的例子來說,內容稍微有些複雜,它的作用是:啟動gazebo模擬器,匯入引數內容,加入機器人模型。
小結
對於初學者,我們不要求掌握每一個標籤是什麼作用,但至少應該有一個印象。如果我們要進行自己寫launch檔案,可以先從改launch檔案的模板。
Topic
3.3.1 簡介
ROS的通訊方式是ROS最為核心的概念,ROS系統的精髓就在於它提供的通訊架構。ROS的通訊方式有以下四種:
- Topic 主題
- Service 服務
- Parameter Service 引數伺服器
- Actionlib 動作庫
3.3.2 Topic
ROS中的通訊方式中,topic是常用的一種。對於實時性、週期性的訊息,使用topic來傳輸是最佳的選擇。topic是一種點對點的單向通訊方式,這裡的“點”指的是node,也就是說node之間可以通過topic方式來傳遞資訊。topic要經歷下面幾步的初始化過程:首先,publisher節點和subscriber節點都要到節點管理器進行註冊,然後publisher會發布topic,subscriber在master的指揮下會訂閱該topic,從而建立起sub-pub之間的通訊。注意整個過程是單向的。其結構示意圖如下:
Subscriber接收訊息會進行處理,一般這個過程叫做回撥(Callback)。所謂回撥就是提前定義好了一個處理函式(寫在程式碼中),當有訊息來就會觸發這個處理函式,函式會對訊息進行處理。
上圖就是ROS的topic通訊方式的流程示意圖。topic通訊屬於一種非同步的通訊方式。下面我們通過一個示例來了解下如何使用topic通訊。
3.3.3 通訊示例
參考下圖,我們以攝像頭畫面的釋出、處理、顯示為例講講topic通訊的流程。在機器人上的攝像頭拍攝程式是一個node(圓圈表示,我們記作node1),當node1執行啟動之後,它作為一個Publisher就開始釋出topic。比如它釋出了一個topic(方框表示),叫做/camera_rgb
,是rgb顏色資訊,即採集到的彩色影象。同時,node2假如是影象處理程式,它訂閱了/camera_rgb
這個topic,經過節點管理器的介紹,它就能建立和攝像頭節點(node1)的連線。
那麼怎麼樣來理解“非同步”這個概念呢?在node1每釋出一次訊息之後,就會繼續執行下一個動作,至於訊息是什麼狀態、被怎樣處理,它不需要了解;而對於node2影象處理程式,它只管接收和處理/camera_rgb
上的訊息,至於是誰發來的,它不會關心。所以node1、node2兩者都是各司其責,不存在協同工作,我們稱這樣的通訊方式是非同步的。
ROS是一種分散式的架構,一個topic可以被多個節點同時釋出,也可以同時被多個節點接收。比如在這個場景中使用者可以再加入一個影象顯示的節點,我們在想看看攝像頭節點的畫面,則可以用自己的筆記本連線到機器人上的節點管理器,然後在自己的電腦上啟動影象顯示節點。
這就體現了分散式系統通訊的好處:擴充套件性好、軟體複用率高。
總結三點:
- topic通訊方式是非同步的,傳送時呼叫publish()方法,傳送完成立即返回,不用等待反饋。
- subscriber通過回撥函式的方式來處理訊息。
- topic可以同時有多個subscribers,也可以同時有多個publishers。ROS中這樣的例子有:/rosout、/tf等等。
3.3.4 操作命令
在實際應用中,我們應該熟悉topic的幾種使用命令,下表詳細的列出了各自的命令及其作用。
命令 | 作用 |
---|---|
rostopic list |
列出當前所有的topic |
rostopic info topic_name |
顯示某個topic的屬性資訊 |
rostopic echo topic_name |
顯示某個topic的內容 |
rostopic pub topic_name ... |
向某個topic釋出內容 |
rostopic bw topic_name |
檢視某個topic的頻寬 |
rostopic hz topic_name |
檢視某個topic的頻率 |
rostopic find topic_type |
查詢某個型別的topic |
rostopic type topic_name |
檢視某個topic的型別(msg) |
如果你一時忘記了命令的寫法,可以通過rostopic help
或rostopic command -h
檢視具體用法。
3.3.5 測試例項
- 首先開啟
ROS-Academy-for-Beginners
的模擬場景,輸入roslaunch robot_sim_demo robot_spawn_launch
,看到我們模擬的模擬環境。該launch
檔案啟動了模擬場景、機器人。 - 檢視當前模擬器中存在的topic,輸入命令
rostopic list
。可以看到許多topic,它們可以視為模擬器與外界互動的介面。 - 查詢topic
/camera/rgb/image_raw
的相關資訊:rostopic info /camera/rgb/image_raw
。則會顯示型別資訊type,釋出者和訂閱者的資訊。 - 上步我們在演示中可以得知,並沒有訂閱者訂閱該主題,我們指定
image_view
來接收這個訊息,執行命令rosrun image_view image_view image:=<image topic> [transport]
。我們可以看到message,即是上一步中的type。 - 同理我們可以查詢攝像頭的深度資訊depth影象。
- 在用鍵盤控制模擬機器人運動的時候,我們可以檢視速度指令topic的內容
rostopic echo /cmd_vel
,可以看到視窗顯示的各種座標引數在不斷的變化。
通過這些例項的測試,幫助我們更快的掌握topic各種操作命令的使用,以及對topic通訊的理解。
小結
topic的通訊方式是ROS中比較常見的單向非同步通訊方式,它在很多時候的通訊是比較易用且高效的。但是有些需要互動的通訊時該方式就顯露出自己的不足之處了,後續我們會介紹雙向同步的通訊方式service。
topic有很嚴格的格式要求,比如上節的攝像頭程序中的rgb影象topic,它就必然要遵循ROS中定義好的rgb影象格式。這種資料格式就是Message。Message按照定義解釋就是topic內容的資料型別,也稱之為topic的格式標準。這裡和我們平常用到的Massage直觀概念有所不同,這裡的Message不單單指一條釋出或者訂閱的訊息,也指定為topic的格式標準。
3.4.2 結構與型別
基本的msg包括bool、int8、int16、int32、int64(以及uint)、float、float64、string、time、duration、header、可變長陣列array[]、固定長度陣列array[C]。那麼具體的一個msg是怎麼組成的呢?我們用一個具體的msg來了解,例如上例中的msg sensor_msg/image
,位置存放在sensor_msgs/msg/image.msg
裡,它的結構如下:
std_msg/Header header
uint32 seq
time stamp
string frame_id
uint32 height
uint32 width
string encoding
uint8 is_bigendian
uint32 step
uint8[] data
觀察上面msg的定義,是不是很類似C語言中的結構體呢?通過具體的定義影象的寬度,高度等等來規範影象的格式。所以這就解釋了Message不僅僅是我們平時理解的一條一條的訊息,而且更是ROS中topic的格式規範。或者可以理解msg是一個“類”,那麼我們每次釋出的內容可以理解為“物件”,這麼對比來理解可能更加容易。 我們實際通常不會把Message概念分的那麼清,通常說Message既指的是類,也是指它的物件。而msg檔案則相當於類的定義了。
3.4.2 操作命令
rosmsg的命令相比topic就比較少了,只有兩個如下:
rosmsg命令 | 作用 |
---|---|
rosmsg list |
列出系統上所有的msg |
rosmsg show msg_name |
顯示某個msg的內容 |
本小節主要介紹常見的message型別,包括std_msgs, sensor_msgs, nav_msgs, geometry_msgs等
Vector3.msg
#檔案位置:geometry_msgs/Vector3.msg
float64 x
float64 y
float64 z
Accel.msg
#定義加速度項,包括線性加速度和角加速度
#檔案位置:geometry_msgs/Accel.msg
Vector3 linear
Vector3 angular
Header.msg
#定義資料的參考時間和參考座標
#檔案位置:std_msgs/Header.msg
uint32 seq #資料ID
time stamp #資料時間戳
string frame_id #資料的參考座標系
Echos.msg
#定義超聲感測器
#檔案位置:自定義msg檔案
Header header
uint16 front_left
uint16 front_center
uint16 front_right
uint16 rear_left
uint16 rear_center
uint16 rear_right
Quaternion.msg
#訊息代表空間中旋轉的四元數
#檔案位置:geometry_msgs/Quaternion.msg
float64 x
float64 y
float64 z
float64 w
Imu.msg
#訊息包含了從慣性原件中得到的資料,加速度為m/^2,角速度為rad/s
#如果所有的測量協方差已知,則需要全部填充進來如果只知道方差,則
#只填充協方差矩陣的對角資料即可
#位置:sensor_msgs/Imu.msg
Header header
Quaternion orientation
float64[9] orientation_covariance
Vector3 angular_velocity
float64[9] angular_velocity_covariance
Vector3 linear_acceleration
float64[] linear_acceleration_covariance
LaserScan.msg
#平面內的鐳射測距掃描資料,注意此訊息型別僅僅適配鐳射測距裝置
#如果有其他型別的測距裝置(如聲吶),需要另外建立不同型別的訊息
#位置:sensor_msgs/LaserScan.msg
Header header #時間戳為接收到第一束鐳射的時間
float32 angle_min #掃描開始時的角度(單位為rad)
float32 angle_max #掃描結束時的角度(單位為rad)
float32 angle_increment #兩次測量之間的角度增量(單位為rad)
float32 time_increment #兩次測量之間的時間增量(單位為s)
float32 scan_time #兩次掃描之間的時間間隔(單位為s)
float32 range_min #距離最小值(m)
float32 range_max #距離最大值(m)
float32[] ranges #測距資料(m,如果資料不在最小資料和最大資料之間,則拋棄)
float32[] intensities #強度,具體單位由測量裝置確定,如果儀器沒有強度測量,則陣列為空即可
Point.msg
#空間中的點的位置
#檔案位置:geometry_msgs/Point.msg
float64 x
float64 y
float64 z
Pose.msg
#訊息定義自由空間中的位姿資訊,包括位置和指向資訊
#檔案位置:geometry_msgs/Pose.msg
Point position
Quaternion orientation
PoseStamped.msg
#定義有時空基準的位姿
#檔案位置:geometry_msgs/PoseStamped.msg
Header header
Pose pose
PoseWithCovariance.msg
#表示空間中含有不確定性的位姿資訊
#檔案位置:geometry_msgs/PoseWithCovariance.msg
Pose pose
float64[36] covariance
Power.msg
#表示電源狀態,是否開啟
#檔案位置:自定義msg檔案
Header header
bool power
######################
bool ON = 1
bool OFF = 0
Twist.msg
#定義空間中物體運動的線速度和角速度
#檔案位置:geometry_msgs/Twist.msg
Vector3 linear
Vector3 angular
TwistWithCovariance.msg
#訊息定義了包含不確定性的速度量,協方差矩陣按行分別表示:
#沿x方向速度的不確定性,沿y方向速度的不確定性,沿z方向速度的不確定性
#繞x轉動角速度的不確定性,繞y軸轉動的角速度的不確定性,繞z軸轉動的
#角速度的不確定性
#檔案位置:geometry_msgs/TwistWithCovariance.msg
Twist twist
float64[36] covariance #分別表示[x; y; z; Rx; Ry; Rz]
Odometry.msg
#訊息描述了自由空間中位置和速度的估計值
#檔案位置:nav_msgs/Odometry.msg
Header header
string child_frame_id
PoseWithCovariance pose
TwistWithCovariance twist