文章目錄
- 簡述
- 特點
- 相關常用指令
- 通信模型
- 核心元素
- 通信過程
- 代碼示例(釋出者和訂閱者)
-
- 釋出者(talker.cpp)
- 訂閱者(listener.cpp)
- 配置 CMakeLists.txt
- 編譯和運作
- 問題擴充
- 自定義消息
-
- 自定義msg檔案
- 編輯配置檔案
- 執行編譯
- 修改訂閱者和釋出者代碼
- 編譯和運作
簡述
話題消息通信是指發送資訊的釋出者和接收資訊的訂閱者以話題消息的形式發送和接收資訊。希望接收話題的訂閱者節點接收的是與在主節點中注冊的話題名稱對應的釋出者節點的資訊。基于這個資訊,訂閱者節點直接連接配接到釋出者節點來發送和接收消息。
特點
話題是單向的,适用于需要連續發送消息的傳感器資料,因為它們通過一次的連接配接連續發送和接收消息。另外,單個釋出者可以與多個訂閱者進行通信,相反,一個訂閱者可以在單個話題上與多個釋出者進行通信。
相關常用指令
指令 | 詳細說明 |
---|---|
rostopic list | 顯示活動的話題目錄 |
rostopic echo [話題名稱] | 實時顯示指定話題的消息内容 |
rostopic find [類型名稱] | 顯示使用指定類型的消息的話題 |
rostopic type [話題名稱] | 顯示指定話題的消息類型 |
rostopic bw [話題名稱] | 顯示指定話題的消息帶寬(bandwidth) |
rostopic hz [話題名稱] | 顯示指定話題的消息資料釋出周期 |
rostopic info [話題名稱] | 顯示指定話題的資訊 |
rostopic pub [話題名稱] [消息類型] [參數] | 用指定的話題名稱釋出消息 |
通信模型
PS:參考原文
核心元素
-
必須首先被運作,并使用XMLRPC伺服器,負責管理節點之間的消息通信中的連接配接資訊。ROS Master(管理者)
-
生成指定的話題,将TCPROS消息釋出給所有的訂閱者。Talker (釋出者)
-
訂閱指定的話題,從釋出者那裡接收指定的TCPROS的消息。Listener (訂閱者)
PS:
XMLRPC
(XML-Remote Procedure Call)是一種RPC協定,其編碼形式采用XML編碼格式,而傳輸方式采用既不保持連接配接狀态、也不檢查連接配接狀态的請求和響應方式的HTTP協定。XMLRPC是一個非常簡單的約定,僅用于定義小資料類型或指令,是以它比較簡單。有了這個特點,XMLRPC非常輕便,支援多種程式設計語言,是以非常适合支援各種硬體和語言的ROS。
通信過程
-
主節點(master)負責節點到節點的連接配接和消息通信,類似于名稱伺服器(Name Server)。roscore是它的運作指令,當運作主節點時,可以注冊每個節點的名字,并根據需要擷取資訊。沒有主節點,就不能在節點之間建立通路和消息交流(如話題和服務)。主節點使用XML遠端過程調用(XMLRPC,XML-Remote Procedure Call)與節點進行通信。運作主節點
-
釋出者節點向主節點注冊釋出者節點名稱、話題名稱、消息類型、URI位址和端口。運作釋出者節點
-
訂閱者節點在運作時向主節點注冊其訂閱者節點名稱、話題名稱、消息類型、URI位址和端口。運作訂閱者節點
-
主節點向訂閱者節點發送此訂閱者希望通路的釋出者的名稱、話題名稱、消息類型、URI位址和端口等資訊。主節點向訂閱者節點發送釋出者節點資訊
-
訂閱者節點根據從主節點接收的釋出者資訊,向釋出者節點請求直接連接配接。在這種情況下,要發送的資訊包括訂閱者節點名稱、話題名稱和消息類型。訂閱者節點向釋出者節點發送連接配接請求
-
釋出者節點将TCP伺服器的URI位址和端口作為連接配接響應發送給訂閱者節點。釋出者節點進行連接配接響應
-
訂閱者節點使用TCPROS建立一個與釋出者節點對應的用戶端,并直接與釋出者節點連接配接。釋出者和訂閱者建立連接配接後,可以直接發送消息,不再通過ROS Master,是以,此時即使關閉掉ROS Master也不影響兩者的通信。釋出者節點和訂閱者節點建立連接配接
-
釋出者節點向訂閱者節點發送消息。節點間通信使用一種稱為TCPROS的TCP/IP方式。發送消息
代碼示例(釋出者和訂閱者)
需求描述:編寫釋出者和訂閱者檔案,釋出者以10HZ(每秒10次)的頻率釋出文本消息,訂閱者訂閱到消息後列印出來。
首先,這裡建立了一個topic_test的包,然後分别建立talker.cpp(釋出者)和listener(訂閱者)兩個檔案。
釋出者(talker.cpp)
#include "ros/ros.h"
#include "std_msgs/UInt32.h"
int main(int argc, char **argv)
{
// 設定編碼
setlocale(LC_ALL, "");
// 1.初始化ROS節點
// 參數3為節點名稱,全局唯一
ros::init(argc, argv, "talker");
// 2.執行個體化ROS句柄
ros::NodeHandle nh;
// 3.執行個體化釋出者對象
// 參數1為資料要釋出到的話題名稱,參數2為緩沖區大小
ros::Publisher pub = nh.advertise<std_msgs::UInt32>("chatter", 1000);
// 4.定義要釋出的資料
int count = 0;
std_msgs::UInt32 msg;
// 5.定義資料釋出的頻率為每秒10次
ros::Rate loop_rate(10);
// ros::ok()表示隻要節點還在運作即滿足循環條件
while (ros::ok())
{
msg.data = count;
// 6.釋出消息
pub.publish(msg);
// 列印發送的資訊
ROS_INFO("發送的消息是:%d", msg.data);
// 處理ROS消息回調,隻循環一次
// 這裡并沒有接收到任何回調,是以這一句可以不寫
ros::spinOnce();
count++;
loop_rate.sleep();
}
return 0;
}
訂閱者(listener.cpp)
#include "ros/ros.h"
#include "std_msgs/UInt32.h"
void chatterCallback(const std_msgs::UInt32::ConstPtr& msg)
{
// 4.處理訂閱的消息
ROS_INFO("訂閱到了消息: [%d]", msg->data);
}
int main(int argc, char **argv)
{
// 設定編碼
setlocale(LC_ALL, "");
// 1.初始化ROS節點
// 參數3為節點名稱,全局唯一
ros::init(argc, argv, "listener");
// 2.執行個體化ROS句柄
ros::NodeHandle nh;
// 3.執行個體化訂閱者者對象
// 參數1為要訂閱的話題名稱,參數2為緩沖區大小,參數3為處理訂閱消息的回調函數
ros::Subscriber sub = nh.subscribe<std_msgs::UInt32>("chatter", 1000, chatterCallback);
// 處理ROS消息回調,一直循環,直到ROS節點退出(執行了Ctrl+C或者ros::shutdown()被調用)
ros::spin();
return 0;
}
配置 CMakeLists.txt
注意:這裡的CMakeLists.txt指的是topic_test包下面的,而非整個工程下面的那個,在CMakeLists.txt檔案中添加下面的内容。
# 節點建構選項,配置可執行檔案
add_executable(talker src/talker.cpp)
add_executable(listener src/listener.cpp)
# 節點建構選項,配置目标連結庫
target_link_libraries(talker
${catkin_LIBRARIES}
)
target_link_libraries(listener
${catkin_LIBRARIES}
)
編譯和運作
使用Ctrl+Shift+B進行編譯,然後使用roscore指令啟動主節點,然後source下環境變量,分别運作釋出者和訂閱者節點即可看到通信資料的列印輸出。
問題擴充
上面先運作了釋出者節點,從0開始列印資訊,然後運作訂閱者節點,資訊直接從196開始列印了,訂閱者運作之前釋出者釋出的資訊就接收不到了。
嘗試先運作訂閱者節點,然後運作釋出者節點,仍然存在資訊丢失的情況。原因是在釋出者開始釋出消息時,其還未在主節點注冊完成。
可以在talker.cpp中添加延時來延遲第一條資料的發送時間。
在延時等待注冊完成後再發送消息,就可以收到完整的消息了。
自定義消息
ROS節點通過消息互相通信,ROS提供了一些标準的資料類型,也提供了一種基于标準消息類型開發自定義消息類型的機制。上面的示例中我們使用的是ROS标準的消息,實際使用中常常需要自定義消息來滿足需求。
ROS的消息類型可以在這裡檢視。
需求描述:建立一個關于學生資訊的msg檔案,其中包含姓名、年齡和成績,釋出者發送學生資訊,訂閱者列印學生資訊。
自定義msg檔案
首先,在topic_test包中建立msg檔案夾,然後,在檔案夾中建立消息檔案(Student.msg)。
string name
uint8 age
float32 score
編輯配置檔案
- 在package.xml中添加編譯依賴與執行依賴
<!-- 編譯時依賴 -->
<build_depend>message_generation</build_depend>
<!-- 運作時依賴 -->
<exec_depend>message_runtime</exec_depend>
- 在CMakeLists.txt編輯 msg 相關配置
# catkin建構時依賴的元件包,前3個建立ROS包時已經自動生成了,這裡添加了message_generation
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 配置msg源檔案,FILES将引用目前功能包目錄的msg目錄中的*.msg檔案,自動生成一個頭檔案(*.h)
add_message_files(
FILES
Student.msg
)
# 生成消息時依賴于std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
# 運作時依賴,描述了庫、catkin建構依賴項和系統依賴的功能包
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES topic_test
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
執行編譯
使用Ctrl+Shift+B進行編譯,然後可以在/devel/topic_test目錄下看到自動生成的頭檔案Student.h,此時便可以代後面的代碼中進行引用了。
PS: 在修改代碼之前,先在c_cpp_properties.json檔案中添加下頭檔案路徑,否則在代碼中引用頭檔案時會出現找不到的情況。
修改訂閱者和釋出者代碼
下面我們在代碼中使用自定義的消息來進行通信。
- 釋出者(talker.cpp)
#include "ros/ros.h"
#include "topic_test/Student.h"
int main(int argc, char **argv)
{
// 設定編碼
setlocale(LC_ALL, "");
// 1.初始化ROS節點
// 參數3為節點名稱,全局唯一
ros::init(argc, argv, "talker");
// 2.執行個體化ROS句柄
ros::NodeHandle nh;
// 3.執行個體化釋出者對象
// 參數1為資料要釋出到的話題名稱,參數2為緩沖區大小
ros::Publisher pub = nh.advertise<topic_test::Student>("chatter", 1000);
// 4.定義要釋出的資料
topic_test::Student msg;
msg.name = "張三";
msg.age = 18;
msg.score = 85.6;
// 5.定義資料釋出的頻率為每秒1次
ros::Rate loop_rate(1);
// ros::ok()表示隻要節點還在運作即滿足循環條件
while (ros::ok())
{
// 6.釋出消息
pub.publish(msg);
// 列印發送的資訊
ROS_INFO("發送的消息是:姓名-%s, 年齡-%d, 成績-%.2f", msg.name.c_str(), msg.age, msg.score);
// 處理ROS消息回調,隻循環一次
// 這裡并沒有接收到任何回調,是以這一句可以不寫
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
- 訂閱者(listener.cpp)
#include "ros/ros.h"
#include "topic_test/Student.h"
void chatterCallback(const topic_test::Student::ConstPtr& msg)
{
// 4.處理訂閱的消息
ROS_INFO("訂閱到了消息: 姓名【%s】, 年齡【%d】, 成績【%.2f】", msg->name.c_str(), msg->age, msg->score);
}
int main(int argc, char **argv)
{
// 設定編碼
setlocale(LC_ALL, "");
// 1.初始化ROS節點
// 參數3為節點名稱,全局唯一
ros::init(argc, argv, "listener");
// 2.執行個體化ROS句柄
ros::NodeHandle nh;
// 3.執行個體化訂閱者者對象
// 參數1為要訂閱的話題名稱,參數2為緩沖區大小,參數3為處理訂閱消息的回調函數
ros::Subscriber sub = nh.subscribe<topic_test::Student>("chatter", 1000, chatterCallback);
// 處理ROS消息回調,一直循環,直到ROS節點退出(執行了Ctrl+C或者ros::shutdown()被調用)
ros::spin();
return 0;
}
編譯和運作
使用Ctrl+Shift+B進行編譯,然後使用roscore指令啟動主節點,然後source下環境變量,分别運作釋出者和訂閱者節點即可看到通信資料的列印輸出。
☝ ★★★ — 傳回 《ROS機器人開發筆記彙總》總目錄 — ★★★ ☝