1 背景知识
1.1 话题是什么
ROS程序由多个功能组成;一个功能由多个节点完成;各节点的功能独立,节点与节点间通过不同的通信方式,实现数据的传输,完成功能。即总功能由分功能组成,各分功能由节点的子功能及节点间的通信完成。
话题是一种节点与节点间进行
一对多、单向传输
的数据通信方式,数据通过话题的发布与订阅实现传送。
1.2 话题的通信机制
参与节点及节点功能:
1. 发布者节点Talker: 发布话题。
2. 订阅者节点Listener: 订阅话题。
3. 节点管理器ROS Master:保管节点注册信息,匹配话题的订阅者和发布者,并帮助订阅者和发布者的建立连接。
通信过程如下:
七步骤如下:
步骤 | 步骤内容 | 类比 |
---|---|---|
1.发布者注册 | 向节点管理器ROS Master注册相关信息,包括:节点信息、发布的话题 | 在婚姻介绍所登记信息 |
2.订阅者注册 | 向节点管理器ROS Master注册相关信息,包括:节点信息、订阅的话题 | 在婚姻介绍所登记信息 |
3.节点管理器进行话题匹配 | 保管注册信息,匹配话题相同的Talker和Listener,通过RPC向Listener发送Talker的RPC地址 | 婚姻介绍所举办相亲会 |
4.订阅者向发送连接请求 | Listener通过RPC向Talker发送连接请求,传输话题名、消息类型、通信协议 | 相亲会上选择中意对象 |
5.发布者确认连接请求 | Talker接收连接请求,通过RPC向Listener确认连接 | 也看上了,坐下 |
6.发布者尝试与订阅者建立网络连接 | Listener接收到确认信息后,通过TCP与Talker建立网络连接。 | 聊得不错,加微信 |
7.发布者向订阅者发布消息 | 成功建立连接后,Talker开始向Listener发送话题消息数据 | 同意好友请求,火热聊天 |
1.3 话题的特点
- 一个话题可以有多个订阅者;一个订阅者可以订阅多个话题,一个发布者也可以发布多个话题。实现一对一、一对多、多对多的通信网络 。
- 话题是一种异步通信方式。话题的发布者只发布话题,发布完成立马返回;话题订阅者收到话题后,通过回调函数处理话题发送的消息。
1.4 验证
打开一终端
$ roscore
打开另一终端
$ rosrun turtlesim turtlesim_node
[ INFO] [1679577658.273288946]: Starting turtlesim with node name /turtlesim
[ INFO] [1679577658.283734154]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
再启另一终端
$ rosrun turtlesim turtle_teleop_key
Reading from keyboard
---------------------------
Use arrow keys to move the turtle. 'q' to quit.
此时,通过输入四个方向键可以使乌龟动起来。
再启另一终端:
$ rosrun rqt_graph rqt_graph
rqt_graph:图形化的工具,画出正在发布主题和订阅的关系图
再启另一终端:
$ rostopic info /turtle1/cmd_vel
Type: geometry_msgs/Twist
Publishers:
* /teleop_turtle (http://ubuntu:45347/)
Subscribers:
* /turtlesim (http://ubuntu:39403/)
上述终端中的信息可以看出, /teleop_turtle节点发布话题 /turtle1/cmd_vel ,/turtlesim节点订阅话题 /turtle1/cmd_vel,但话题名上看不出传递了什么信息。
话题要实现信息传递,实际上是通过话题下的
消息message
,例如:话题/turtle1/cmd_vel的消息是[geometry_msgs/Twist] ,话题和消息的关系可以类比成报纸和报纸上的内容。
话题的消息数据类型是根据需要决定的。查看消息[geometry_msgs/Twist] 传递数据:
$ rosmsg show geometry_msgs/Twist
geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z
上述过程验证了节点Node通过
发布和订阅Topic
实现信息的传递。
前提
:
ROS 创建工作空间和软件包
当前所处软件包为beginner_tutorials,该软件包的文件系统结构如下图:
2 自定义消息
话题消息的数据结构:为
单个
数据结构。即由
一组
字段定义,字段间的顺序无关紧要。
一个
字段由类型和名称组成,一个字段的结构:<type>空格<name> = <value><type>字段的类型:数据类型 + 数组说明符[](可选)
→数据类型:内置类型 或者 非内置类型
→数组说明符[]:有界[N] 或者 无界[]<name> 字段的名称: 由小写字母、数字、下划线组成
= <value>字段值:可选, ROS1中表明该字段是常量#注释
自定义话题消息文件的后缀为.msg,消息名称与软件包名的组合实现唯一标识消息。
2.1 创建msg文件夹(专放消息), 并编辑Example.msg 文件**
$ roscd beginner_tutorials
$ mkdir msg
$ cd msg
$ touch Example.msg
编辑Example.msg 文件
int64 num #内置类型
std_msgs/String str2 #ROS其他消息包
char[] ch #数组
string str1 = "hello" #常量,此行的注释在实例时不要添加,会被认为是字符串内容
2.2 添加msg依赖
编辑 ‘
软件包
’ 下的package.xml 文件(修改后)
#下面两行取消注释,不存在,则添加
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
# <build_depend> 编译时的依赖
# <exec_depend> 运行时的依赖
编辑‘
软件包
’ 下CMakeLists.txt 文件(修改后)
# 需添加部分1:添加消息生成依赖
find_package(catkin REQUIRED COMPONENTS
.......
message_generation
)
# 需添加部分2 :添加运行时依赖关系
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
# 需修改部分3:
# 1)删除前面的# 符号
# 2)替换Message1.msg,Message2.msg 为 Example.msg
add_message_files(
FILES
Example.msg
)
# 需修改部分4: 删除前面的# 符号 ,int64是规定的ros标准消息类型,属于消息包std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
查看消息是否创建成功:
$ rosmsg show beginner_tutorials/Example
string str1="hello"
int64 num
std_msgs/String str2
string data
char[] ch
std_msgs/String是ROS的标准数据包。
2.3 编译,使.msg能被转换为C++、Python和其他语言的源代码
要使用自定义消息,需将.msg 文件编译为 .h 文件,再引入到程序中 。
$ cd ~/catkin_ws
$ catkin_make
编译后,生成相应的Example.h。
Example.h头文件位置在:
./devel/include/beginner_tutorials/Example.h
3 编辑发布者
- 初始化节点(命名,唯一) 。
- 实例化句柄 。
- 实例化发布者对象,发布的话题及话题的消息数据。
- 组织发布的数据。
- 编译。
3.1 编辑talker.cpp
在软件包的src文件中,创建talker.cpp文件。
$ touch talker.cpp
编辑talker.cpp文件:
#include "ros/ros.h"
#include "beginner_tutorials/Example.h" //自定义消息包
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");//初始化节点,标识"listener"的节点名, 需唯一
ros::NodeHandle n;//初始化句柄
ros::Publisher chatter_pub = n.advertise<beginner_tutorials::Example>("chatter", 1000);
//定义发布者,发布话题"chatter"
ros::Rate loop_rate(10);
beginner_tutorials::Example example; //定义消息对象,以组织发布的数据
int count = 0;
while (ros::ok())
{
example.str2.data = "talker";
example.num = count;
example.ch.push_back('A');
ROS_INFO(" %s say :%s, the %c part is %ld ", example.str2.data.c_str(), example.str1.c_str(), example.ch.at(0), example.num);
++count;
chatter_pub.publish(example); //发布话题
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
3.2 编译,生成可执行文件
编辑CMakeLists.txt,增添内容:
#生成可执行文件
add_executable(listener src/listener_msg.cpp)
#需要连接的库
target_link_libraries(listener ${catkin_LIBRARIES})
#需要的依赖
add_dependencies(listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
在工作空间下编译,生成可执行文件。
$ cd catkin_ws
$ catkin_make
$ source ./devel/setup.bash
可执行文件放置于:devel/lib/beginner_tutorials
4 编辑订阅者
- 初始化节点(命名,唯一) 。
- 实例化句柄 。
- 实例化订阅者对象,订阅的话题,处理话题消息的回调函数。
- 定义处理订阅的消息(回调函数)。
- 设置循环,以调用回调函数。
- 编译。
4.1 编辑listener.cpp
在
软件包
的src文件中,创建listener.cpp
$ touch listener.cpp
编辑 listener.cpp
# include "ros/ros.h"
#include "beginner_tutorials/Example.h" //自定义消息
//回调函数:处理订阅消息
void chatterCallback(const beginner_tutorials::Example::ConstPtr& msg)
{
ROS_INFO("listener get the value is : [%ld]", msg->num);
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");//初始化节点,标识"listener"的节点名, 需唯一
ros::NodeHandle n; //初始化句柄
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
//定义订阅者,订阅话题"chatter",绑定回调函数
ros::spin();//设置循环,调用回调函数
return 0;
}
回调函数的要求:
- 参数只能有一个且必须以const修饰
- 参数类型为xxxConstPtr
- 参数为引用传递
- 函数没有返回值
4.2 编译,生成可执行文件
编辑CMakeLists.txt,增添内容:
add_executable(talker src/talker_msg.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
在工作空间下编译, 生成可执行文件。
$ cd catkin_ws
$ catkin_make
$ source ./devel/setup.bash
可执行文件放置于:devel/lib/beginner_tutorials
5 验证
打开终端,运行roscore。
$ roscore
另起终端,先运行订阅者listener节点。
$ rosrun beginner_tutorials listener
再另起终端,运行发布者talker节点
$ rosrun beginner_tutorials talker
结果:
当前软件包的文件系统结构:(红框为当前教程新增)
6 总结
6.1 ros指令总结
:
roscore 运行节点管理器
rosrun <package_name> <node_Name> : 运行软件包的节点
rospack info <topic_name> :打印有关活动主题的详细信息
rosmsg info <package_name>/<\message_name> : 打印与话题相关的发布者订阅者信息
rosmsg show <package_name>/<\message_name> : 查看话题的消息
6.2 核心函数
: