前言
我要給大家來介紹一下ROS當中一些核心的概念,幫助大家去在後面的ROS學習當中更快地吸收這些概念,今天講解的是ROS中的通信機制——話題通信。
ROS系列文章
ROS的通信機制
從前面的文章我們知道,ROS的通信機制是一個 鬆散耦合的分佈式軟件框架
設計而來的,而節點間的通訊就會必然是有幾種通訊方式,那ROS主要給節點之間提供三種核心的通訊方式,一種叫做話題(topic),一種叫做服務(server),以及基於RPC的參數服務器。
我們接下來講解ROS的核心概念——ROS話題通信機制。
話題(topic)
話題
就是ROS中一個數據傳輸的有名字的通道。當一個節點想要分享信息時,它就會發布(publish)消息到對應的一個或者多個話題;當一個節點想要接收信息時,它就會訂閱(subscribe)它所需要的一個或者多個話題。 ROS節點管理器負責確保發佈節點和訂閱節點能找到對方;而且消息是直接地從發佈節點傳遞到訂閱節點,中間並不經過節點管理器轉交。
topic這個單詞對我來說是非常熟悉的,因爲我在物聯網領域老是搗鼓網絡協議棧,在MQTT協議裏面經常看到topic,巧合的是ROS中的topic跟MQTT協議中的topic是非常像的,基本可以說是同樣的模型,但MQTT協議裏面需要服務器轉發數據。
比如我們看下面這張圖:
話題通訊的模型比較簡單,這張圖的模型裏面會分爲發佈者(publisher)和訂閱者(subscriber),話題的這種通訊的方式是單向數據傳輸。比如說有一個節點它要發佈一個數據,這個攝像頭驅動節點它來驅動攝像頭,並且獲取攝像頭的數據,然後傳出來給處理數據的節點去處理,所以它會不斷的往外發送一些圖像數據,那它發到哪裏呢?這個節點作爲發佈者,需要向某個話題(topic,也可以稱爲主題)
發佈這些數據,那麼當系統中可能有多個節點要去處理圖像,比如有一個節點是需要顯示圖像數據的,有一個節點是做圖像處理的,它們實現要獲取到這些數據後,才能處理對吧,而這些節點就是訂閱者,它們從話題(topic)
中接收這些數據,然後去處理它。
發佈者
發佈者:發佈(publish)是指以將數據發佈到某個話題中,爲了可以正常執行發佈操作。發佈者(publisher)必須在節點管理器上註冊自己的話題等多種信息。
發佈者的工作過程如下:
-
向節點管理器註冊節點。
-
告訴管理器要向哪個話題發佈數據。
-
節點運行時採集數據,然後發佈到這個話題。
訂閱者
訂閱是指訂閱者節點想要接收來自某個話題的數據。爲了可以正常執行訂閱的操作,訂閱者節點必須在節點管理器上註冊自己想要訂閱的話題等多種信息,並從節點管理器接收來自發布者的數據信息,同一個話題可以有多個發佈者也可以有多個訂閱者,但一般情況下發布者只有一個,而訂閱者是一個或者多個。
訂閱者的工作過程如下:
-
向節點管理器註冊節點。
-
訂閱話題,告訴管理器要接收哪個話題的數據。
-
節點運行時就接收數據,然後去處理數據(一般來說會註冊回調函數,當有數據的時候就在回調函數中處理)。
總的來說,話題就像是一個數據傳輸的管道。打個比方,你可以在運輸牛奶的管道接到牛奶,在運輸煤氣的管道上接收到煤氣,在水管上接到水。。。
就拿上面的圖來說,Camera Node
節點是發佈者,它負責採集圖像數據並且發佈;Image Processing Node
節點是訂閱者,在接收到圖像數據後要去處理數據,Image Display Node
節點也是訂閱者,在接收到圖像數據後要去顯示圖像的數據。
話題通訊它是一種異步通訊,因爲我們沒有辦法知道發佈者跟訂閱者之間單向傳輸的這個時效性啊,我可能發不出去之後,訂閱者可能等很長時間啊或者阻塞纔會接收,到那很多情況下,我們更希望我的數據發出去之後,對方能給我一個。
消息(Message)
大家有沒有考慮過,話題之間的數據叫什麼呢?沒錯,就是叫消息。
節點之間通過消息(message)來發送和接收數據。消息是用來描述我們傳輸話題數據的,消息可以是各種類型的數據,比如整形、浮點型、字符等不定程度的數據,我們還可以在消息中使用一些數據結構來描述這些消息。
用C++來寫話題通信的代碼
因爲本系列文章還沒介紹到工作空間,那麼就先簡單看看怎麼去寫代碼吧:
- 頭文件
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
發佈者
- 創建節點句柄,並且告訴節點管理器準備向
test_topic
話題發佈消息。
// ROS節點初始化
ros::init(argc, argv, "publisher_topic");
// 創建節點句柄
ros::NodeHandle n;
// 創建一個Publisher,發佈名爲test_topic,消息類型爲gstd_msgs::String,隊列長度100
ros::Publisher pub_topic = n.advertise<std_msgs::String>("test_topic", 100);
- 填充消息的內容並且向話題發佈消息:
// 初始化std_msgs::String類型的消息
std_msgs::String str_msg;
str_msg.data = "this is a test_topic message!";
// 發佈消息
pub_topic.publish(str_msg);
訂閱者
- 創建節點句柄,並且告訴節點管理器訂閱一個話題
test_topic
// 接收到訂閱的消息後,會進入消息回調函數
void test_topic_cb(const std_msgs::String::ConstPtr& msg)
{
// 將接收到的消息打印出來
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
// 初始化ROS節點
ros::init(argc, argv, "subscriber_topic");
// 創建節點句柄
ros::NodeHandle n;
// 創建一個Subscriber,訂閱名爲test_topic的消息,註冊回調函數test_topic_cb
ros::Subscriber sub_topic = n.subscribe("test_topic", 100, test_topic_cb);
- 循環等待來自話題
test_topic
的消息,當收到消息後會進入回調函數test_topic_cb()
中處理:
// 循環等待回調函數
ros::spin();
此處需要在工作空間進行實驗,分別打開新的終端運行節點管理器、發佈者節點、訂閱者節點,實驗效果如下:
使用rqt_graph
publisher_topic
節點和subscriber_topic
節點之間是通過一個test_topic
話題來互相通信的,我們可以使用rqt_graph
來顯示當前運行的節點和話題。
rqt_graph是rqt程序包中的一部分,如果你沒有安裝,請通過以下命令來安裝:
sudo apt-get install ros-<distro>-rqt
sudo apt-get install ros-<distro>-rqt-common-plugins
將 <distro>
替換成你所安裝的版本(比如 kinetic、melodic 等)。
在一個新終端中運行:
rosrun rqt_graph rqt_graph
你會看到以下界面:
源碼附錄
- subscriber_topic.cpp:
#include <ros/ros.h>
#include <std_msgs/String.h>
// 接收到訂閱的消息後,會進入消息回調函數
void test_topic_cb(const std_msgs::String::ConstPtr& msg)
{
// 將接收到的消息打印出來
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
// 初始化ROS節點
ros::init(argc, argv, "subscriber_topic");
// 創建節點句柄
ros::NodeHandle n;
// 創建一個Subscriber,訂閱名爲test_topic的消息,註冊回調函數test_topic_cb
ros::Subscriber sub_topic = n.subscribe("test_topic", 100, test_topic_cb);
// 循環等待回調函數
ros::spin();
return 0;
}
- publisher_topic.cpp:
#include <ros/ros.h>
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
// ROS節點初始化
ros::init(argc, argv, "publisher_topic");
// 創建節點句柄
ros::NodeHandle n;
// 創建一個Publisher,發佈名爲test_topic,消息類型爲gstd_msgs::String,隊列長度100
ros::Publisher pub_topic = n.advertise<std_msgs::String>("test_topic", 100);
// 設置循環的頻率
ros::Rate loop_rate(10);
while (ros::ok())
{
// 初始化std_msgs::String類型的消息
std_msgs::String str_msg;
str_msg.data = "this is a test_topic message!";
// 發佈消息
pub_topic.publish(str_msg);
// 按照循環頻率延時
loop_rate.sleep();
}
return 0;
}
當然你也可以用Python語言來實現話題通信,可以參考我後文中給出的鏈接~
引用說明
本文的部分截圖來自《古月居·ROS入門21講》的課件。