[ROS Study Notes 4] Topic Communication
Article directory
- [ROS Study Notes 4] Topic Communication
-
- 0. Topic communication overview
- 1. Theoretical model of topic communication
- 2. Cpp implementation of basic operations of topic communication
- 3. Python implementation of basic operations of topic communication
- 4. Custom msg for topic communication
- 5. Cpp implementation of topic communication custom msg call
- 6. Python implementation of topic communication custom msg
- 7. Reference
0. Topic communication overview
Topic communication is the most frequently used communication method in ROS. Topic communication is based on the publish-subscribe model, that is: one node publishes a message, and another node subscribes to the message. The application scenarios of topic communication are also extremely extensive.
concept
A communication mode that implements data interaction between different nodes in a publish-subscribe manner
effect
For data transfer scenarios that are constantly updated and less logically processed.
1. Theoretical model of topic communication
The implementation model of topic communication is relatively complicated. The model is shown in the figure below. There are three roles involved in the model:
- ROS Master (manager)
- Talker (Publisher)
- Listener (subscriber)
ROS Master is responsible for keeping the registered information of Talker and Listener, matching Talker and Listener with the same topic, and helping Talker and Listener establish a connection. After the connection is established, Talker can publish messages, and the published messages will be subscribed by Listener.
The whole process is realized by the following steps:
0. Talker registration
After Talker starts, it will register its own information in ROS Master through RPC, which includes the topic name of the published message. ROS Master will add the registration information of the node to the registry.
1. Listener registration
After the Listener is started, it will also register its own information in the ROS Master through RPC, including the topic name that needs to subscribe to the message. ROS Master will add the registration information of the node to the registry.
2. ROS Master implements information matching
ROS Master will match Talker and Listener according to the information in the registry, and send Talker's RPC address information to Listener through RPC.
3. Listener sends a request to Talker
After the Talker receives the Listener's request, it also confirms the connection information to the Listener through RPC, and sends its own TCP address information.
4. The Listener establishes a connection with the Talker
The Listener uses TCP to establish a network connection with the Talker according to the message returned in step 4.
5. Talker sends message to Listener
After the connection is established, the Talker starts publishing messages to the Listener
Attention1: In the above implementation process, the first five steps use the RPC protocol, and the last two steps use the TCP protocol
Attention2: There is no sequence requirement for the startup of Talker and Listener
Attention3: Both Talker and Listener can have multiple
Attention4: After the connection between Talker and Listener is established, ROS Master is no longer needed
Attention5: The above process has been encapsulated and can be called directly in the future.
Topic communication focus:
Point1: Most of the implementation has been encapsulated
Point2: topic setting
Point3: Focus on the implementation of the publisher
Point4: Focus on the implementation of subscribers
Point5: Pay attention to the message carrier
2. Cpp implementation of basic operations of topic communication
1. Demand
Write a publish-subscribe implementation, requiring the publisher to publish a text message at a frequency of 10HZ, and the subscriber to print out the message content
2. Analysis
In the model implementation, ROS Master has been automatically implemented, we don’t need to implement it again, and the connection package has also been packaged. There are three points that need attention
- Publisher
- receiver
- data (plain text here)
3. Process
- Write the publisher implementation
- Write the Subscriber Implementation
- edit configuration file
- compile and execute
4. Implementation by the publisher
/*
需求: 实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
PS: 二者需要设置相同的话题
消息发布方:
循环发布信息:HelloWorld 后缀数字编号
实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化 发布者 对象
5.组织被发布的数据,并编写逻辑发布数据
*/
// 1.包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
// 参数1和参数2 后期为节点传值会使用
// 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
ros::init(argc,argv,"talker");
//3.实例化 ROS 句柄
ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
//4.实例化 发布者 对象
//泛型: 发布的消息类型
//参数1: 要发布到的话题
//参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
//5.组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
// msg.data = "你好啊!!!";
std::string msg_front = "Hello 你好!"; //消息前缀
int count = 0; //消息计数器
//逻辑(一秒10次)
ros::Rate r(1);
//节点不死
while (ros::ok())
{
//使用 stringstream 拼接字符串与编号
std::stringstream ss;
ss << msg_front << count;
msg.data = ss.str();
//发布消息
pub.publish(msg);
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
//根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
r.sleep();
count++;//循环结束前,让 count 自增
//暂无应用
ros::spinOnce();
}
return 0;
}
5. Realization of subscribers
/*
需求: 实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
消息订阅方:
订阅话题并打印接收到的消息
实现流程:
1.包含头文件
2.初始化 ROS 节点:命名(唯一)
3.实例化 ROS 句柄
4.实例化 订阅者 对象
5.处理订阅的消息(回调函数)
6.设置循环调用回调函数
*/
// 1.包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
void doMsg(const std_msgs::String::ConstPtr& msg_p){
ROS_INFO("我听见:%s",msg_p->data.c_str());
// ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化 ROS 节点:命名(唯一)
ros::init(argc,argv,"listener");
//3.实例化 ROS 句柄
ros::NodeHandle nh;
//4.实例化 订阅者 对象
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
//5.处理订阅的消息(回调函数)
// 6.设置循环调用回调函数
ros::spin();//循环读取接收的数据,并调用回调函数处理
return 0;
}
6. Configure CMakeLists.txt
add_executable(Hello_pub
src/Hello_pub.cpp
)
add_executable(Hello_sub
src/Hello_sub.cpp
)
target_link_libraries(Hello_pub
${catkin_LIBRARIES}
)
target_link_libraries(Hello_sub
${catkin_LIBRARIES}
)
7. Execution
- start roscore
- Start the publishing node
- Start the subscription node
The final effect is as follows, you rosrun rqt_graph rqt_graph
can view the calculation graph with
3. Python implementation of basic operations of topic communication
1. Process
- Write the publisher implementation
- Write the Subscriber Implementation
- Add executable permissions to python files
- edit configuration file
- compile and execute
2. Publisher
#! /usr/bin/env python
"""
需求: 实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
PS: 二者需要设置相同的话题
消息发布方:
循环发布信息:HelloWorld 后缀数字编号
实现流程:
1.导包
2.初始化 ROS 节点:命名(唯一)
3.实例化 发布者 对象
4.组织被发布的数据,并编写逻辑发布数据
"""
#1.导包
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
#2.初始化 ROS 节点:命名(唯一)
rospy.init_node("talker_p")
#3.实例化 发布者 对象
pub = rospy.Publisher("chatter",String,queue_size=10)
#4.组织被发布的数据,并编写逻辑发布数据
msg = String() #创建 msg 对象
msg_front = "hello 你好"
count = 0 #计数器
# 设置循环频率
rate = rospy.Rate(1)
while not rospy.is_shutdown():
#拼接字符串
msg.data = msg_front + str(count)
pub.publish(msg)
rate.sleep()
rospy.loginfo("写出的数据:%s",msg.data)
count += 1
2. Subscriber
#! /usr/bin/env python
"""
需求: 实现基本的话题通信,一方发布数据,一方接收数据,
实现的关键点:
1.发送方
2.接收方
3.数据(此处为普通文本)
消息订阅方:
订阅话题并打印接收到的消息
实现流程:
1.导包
2.初始化 ROS 节点:命名(唯一)
3.实例化 订阅者 对象
4.处理订阅的消息(回调函数)
5.设置循环调用回调函数
"""
#1.导包
import rospy
from std_msgs.msg import String
def doMsg(msg):
rospy.loginfo("I heard:%s",msg.data)
if __name__ == "__main__":
#2.初始化 ROS 节点:命名(唯一)
rospy.init_node("listener_p")
#3.实例化 订阅者 对象
sub = rospy.Subscriber("chatter",String,doMsg,queue_size=10)
#4.处理订阅的消息(回调函数)
#5.设置循环调用回调函数
rospy.spin()
3. Add executable permissions
cd scripts
chomd +x *.py
4. Configure CMakeLists.txt
catkin_install_python(PROGRAMS
scripts/talker_p.py
scripts/listener_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
specific implementation effect
4. Custom msg for topic communication
In the ROS communication protocol, the data carrier is an important part. ROS encapsulates some native data types through std_msgs, such as: String, Int32, Int64, Char, Bool, Empty... However, these data generally only contain one data The single field and structure means functional limitations. When transmitting some complex data, such as: lidar information... std_msgs is powerless due to poor descriptiveness. In this scenario, a custom message type can be used
msgs are simply text files, each line has a field type and field name, the field types that can be used are:
- int8, int16, int32, int64 (or unsigned type: uint*)
- float32,float64
- string
- time,duration
- other msg files
- variable-length array[] and fixed-length array[C]
There is also a special type in ROS: Header
, the header contains the timestamp and coordinate frame information commonly used in ROS, and it is often seen that the first line of the msg file has a Header
header.
1. Demand
Create a custom message that includes information about the person: name, height, age, etc.
2. Process
- Create msg files in a fixed format
- edit configuration file
- Compile and generate intermediate files that can be called by Python and Cpp
3. Define the msg file
Create a new msg directory under the function package and add filesPerson.msg
string name
uint16 age
float64 height
4. Edit configuration file
package.xml
Add compilation dependencies and execution dependencies to
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!--
exce_depend 以前对应的是 run_depend 现在非法
-->
CMakeLists.txt
Edit msg related configuration
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
## 配置 msg 源文件
add_message_files(
FILES
Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo02_talker_listener
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
5. Compile
Compiled intermediate file view
The intermediate file that C++ needs to call (.../workspace/devel/include/package name/xxx.h)
Intermediate files that Python needs to call (.../workspace/devel/lib/python3/dist-packages/package name/msg)
Subsequent calls to related msgs are called from these intermediate files
5. Cpp implementation of topic communication custom msg call
0. vscode configuration
In order to facilitate code prompts and avoid throwing exceptions by mistake, you need to configure vscode first, and configure the previously generated head
file path into c_pp_properties.json
the includepath
properties:
{
"configurations": [
{
"browse": {
"databaseFilename": "",
"limitSymbolsToIncludedHeaders": true
},
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}
1. Publisher
/*
需求: 循环发布人的信息
*/
#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"talker_person");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建发布者对象
ros::Publisher pub = nh.advertise<demo02_talker_listener::Person>("chatter_person",1000);
//4.组织被发布的消息,编写发布逻辑并发布消息
demo02_talker_listener::Person p;
p.name = "sunwukong";
p.age = 2000;
p.height = 1.45;
ros::Rate r(1);
while (ros::ok())
{
pub.publish(p);
p.age += 1;
ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);
r.sleep();
ros::spinOnce();
}
return 0;
}
2. Subscriber
/*
需求: 订阅人的信息
*/
#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"
void doPerson(const demo02_talker_listener::Person::ConstPtr& person_p){
ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//1.初始化 ROS 节点
ros::init(argc,argv,"listener_person");
//2.创建 ROS 句柄
ros::NodeHandle nh;
//3.创建订阅对象
ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);
//4.回调函数中处理 person
//5.ros::spin();
ros::spin();
return 0;
}
3. Configure CMakeLists.txt
Need to add add_dependencies
intermediate files to set the dependent messages
add_executable(person_talker src/person_talker.cpp)
add_executable(person_listener src/person_listener.cpp)
add_dependencies(person_talker ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(person_listener ${PROJECT_NAME}_generate_messages_cpp)
target_link_libraries(person_talker
${catkin_LIBRARIES}
)
target_link_libraries(person_listener
${catkin_LIBRARIES}
)
4. Execution
1. Start roscore
2. Start the publishing node
3. Start the subscription node
The effect is shown in the figure
6. Python implementation of topic communication custom msg
0. vscode configuration
In order to facilitate code prompts and throw exceptions by mistake, you need to configure vscode first, and configure the previously generated python file path intosettings.json
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/xxx/yyy工作空间/devel/lib/python3/dist-packages"
]
}
1. Publisher
#! /usr/bin/env python
"""
发布方:
循环发送消息
"""
import rospy
from demo02_talker_listener.msg import Person
if __name__ == "__main__":
#1.初始化 ROS 节点
rospy.init_node("talker_person_p")
#2.创建发布者对象
pub = rospy.Publisher("chatter_person",Person,queue_size=10)
#3.组织消息
p = Person()
p.name = "葫芦瓦"
p.age = 18
p.height = 0.75
#4.编写消息发布逻辑
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(p) #发布消息
rate.sleep() #休眠
rospy.loginfo("姓名:%s, 年龄:%d, 身高:%.2f",p.name, p.age, p.height)
2. Subscriber
#! /usr/bin/env python
"""
订阅方:
订阅消息
"""
import rospy
from demo02_talker_listener.msg import Person
def doPerson(p):
rospy.loginfo("接收到的人的信息:%s, %d, %.2f",p.name, p.age, p.height)
if __name__ == "__main__":
#1.初始化节点
rospy.init_node("listener_person_p")
#2.创建订阅者对象
sub = rospy.Subscriber("chatter_person",Person,doPerson,queue_size=10)
rospy.spin() #4.循环
3. Permission configuration
chmod +x *.py
4. Configure CMakeLists.txt
catkin_install_python(PROGRAMS
scripts/talker_p.py
scripts/listener_p.py
scripts/person_talker.py
scripts/person_listener.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
5. Execution
1. Start roscore;
2. Start the publishing node;
3. Start the subscription node.
The effect is as follows: