目录
第一章:ROS通讯之话题(topic)通讯
ROS提供的通讯系统分为两大类:基于TCPROS/UDPROS的Node节点间的进程通讯;基于Nodelet的进程内通讯。所有的软件功能和相关工具的实现都是基于以上通讯。
ros的底层与核心是分布式通讯机制。ROS所有的软件功能和工具都建立在这种分布式通讯机制上。ROS下最核心的三种核心通讯机制:
- 话题通讯机制
- 服务通讯机制
- 参数管理机制
本章我们详解最最最常见的,做ros开发每天都会打交道的话题通讯机制。
一、topic通讯之基础篇
topic通讯的具体实现是通过node之间传递message进行信息传输。
1. Node Master大管家
node master在整个通讯架构中相当于管理中心,是管理整个node通讯的大管家。node首先在master处进行注册,之后master会将该node纳入整个ROS程序中。当ROS程序启动时,第一步首先启动master,然后节点管理器处理依次启动node。
功能:
- 节点间的相互查找,建立连接
- 为系统提供参数服务器,管理全局参数
在终端启动node master的命令:
roscore
启动后可以看到对应参数与节点信息。
2. Node节点
节点就是ros中执行运算任务的进程,一个功能模块通常由多个节点组成。多个节点同时运行,之间进行信息交换与运行时,构成节点关系图。
就topic通讯而言,所对应的节点具体分为发布节点publisher和订阅节点subscriber.
publisher用于发布对应的topic话题,subscriber用于订阅publisher发布的消息。
3. message 与topic
massage是节点之间传递的信息的数据格式。ros支持标准数据类型,同样也支持数据类型的嵌套以及自定义消息类型。ros本身也定义了一整套用于表征机器人的数据格式,包括速度、位姿、轨迹、点云等等。
topic是传递信息的名称。无论node是发布者还是订阅者,他们之间相互并不了解,他们在各取所需的时候,寻找的都是topic名称。多个node可以往同一个topic话题上发布信息,同样地,多个node也可以订阅同一个topic。他们的关系如下如所示:
4. 小结
两个node之间建立数据通讯需要经过上图所示的7步骤:
- talker注册:通过1234端口想master注册信息,其中包括话题名
- listener注册
- ros master信息匹配:通过listener的订阅信息,在注册列表中查找,没有找到发布者,就等待发布者的加入;找到的话,就通过RPC向listener发布talker的RPC地址信息。
- listener发送连接请求:根据master给的地址,通过RPC向talker发送连接请求、话题名、消息类型、通讯协议
- talker确认连接请求:通过RPC向listener确认连接信息,其中包含TCP地址
- listener尝试与talker建立网络连接
- talker向listener发布数据
两点需要注意:
- 前五个步骤都是RPC通信协议,最后才用到TCP
- master在节点建立连接的过程中起到关键作用,但是不参与节点间最终的数据传输
5. 实例
接下来,我们通过操纵小海龟案例,具体学习日常使用topic通讯过程中常用到的几个命令:
首先roscore
将大管家启动,然后分别使用rosrun
命令启动两个node节点:
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
启动之后,我们通过命令rosnode list
来查看所有启动的节点:
可以看到其中除了master大管家负责的/rosout节点外,我们依次启动了另外的两个节点:
分别名为**/turtlesim节点和/teleop_turtle**节点,第一个节点作用是启动小海龟仿真,第二个节点作用是通过发布对应topic控制小海龟移动,效果如下:
到底是怎么实现的控制呢,我们来详细分析。
首先输入rosrun rqt_graph rqt_graph
查看节点连接图:
如上图所示,很清晰表明,第二个节点**/teleop_turtle通过发布名为/turtle1/cmd_vel
的topic来给到订阅者/tuetlesim**.
其中关键topic的名字是/turtle1/cmd_vel
,通过命令rostopic info /turtle1/cmd_vel
具体查看该topic的相关信息:
上图具体说明了,该topic的数据类型为geometry_msgs/Twist,对应的publisher和subscribers也和节点流通图一一对应。
如果我们想知道具体数据类型中包括那些信息,可以通过如下命令查询:
rosmsg show geometry_msgs/Twist
该命令输出结果如上,数据中具体包括三轴线速度和三轴角速度,以此用来控制小乌龟行动。
到现在为止,我想我已经说清楚ros话题通讯模式了,同样,我们学习了几个非常有用的命令,来启动node,查询node节点图,查看topic和msg等。
最后,我们通过命令来实现往对应的topic上pub消息控制机器人运动。
键入如下命令让小乌龟持续转起来,最后-r 1的参数含义是,按照1hz的频率pub这条topic:
rostopic pub /turtle1/cmd_vel geometry_msgs/Twist "linear:
x: 1.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 1.0" -r 1
那最终实现的样子如下:
二、topic通讯之进阶篇
本部分,我们自定义一个message格式,然后使用ros提供的python和c++接口分别创建两个node节点作为publisher和listener,用来学习python接口下和c++接口下,如何创建node节点,如何订阅和发布topic
1. 创建learn_ topic功能包
首先需要创建对应的功能包,在home目录下输入如下命令:
mkdir -p catkin_ws/src
cd catkin_ws/src
catkin_create_pkg learn_topic std_msgs rospy roscpp
cd ..
catkin_make
我们建立了名为catkin_ws
的工作空间,其中包含名为learn_topic
的功能包,功能包依赖std_msgs
、rospy
、roscpp
三个功能包。
最后我们使用catkin_make
命令编译环境,自动在工作空间目录下生成devel和build两个文件夹。
创建好功能包之后,如果想运行,需要设置环境变量:
source devel/setup.bash
为测试是否已经添加环境变量,使用如下命令查询:
rospack find learn_topic
如果出现上述对应路径,说明环境变量设置成功。
上述设置环境变量的方法,仅仅在当前终端环境下有效,当我们重开新的terminal就需要重新source
为实现一劳永逸的配置,我们可以使用如下命令将之写道bashrc文件中:
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
2. 自定义message
我们来构建一个名称为person.msg
的message文件,其中包括两个信息:
string name
uint8 age
然后在工作空间下的src文件夹中新建msg文件夹,专门来放我们的自定义message. 当前空间目录如下:
要想使自定义的msg工作起来,还需要修改
CMakeLists.txt
文件和package.xml
文件。
在package.xml
文件的文件末尾添加如下语句,实现对应功能包的编译和运行的相关依赖:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
在CMakeLists.txt
文件中,需要进行如下修改:
第一块:修改find_package部分,确保编译时找到对应文件
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
第二块:设置msg文件
add_message_files(
FILES
person.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
第三块:catkin依赖部分
如果我们编写的ros程序不打算给别人使用,这块就无所谓。
catkin_package(
...
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
...
)
上述修改完成之后,回到工作空间下,再次运行命令catkin_make
进行编译
最后查询是否成功,可以使用如下命令:
rosmsg show learn_topic/person
一切正常的话,应该会出现下图:
关于ros中cmakelists文件中内容的具体含义,建议参考我之前的这篇博文:
https://blog.csdn.net/allenhsu6/article/details/112345029
3. 创建node实现message的发布与订阅(C++版本)
接下来,我们依赖上述定义好的person.msg来实现一个简单的publisher和subscriber
在功能包中src文件下分别建立两个cpp文件,如图所示:
publisher
编辑cpp_talker.cpp文件:
#include <ros/ros.h>
#include <learn_topic/person.h>
int main(int argc, char **argv){
ros::init(argc, argv, "cpp_talker");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<learn_topic::person>("/person_topic", 1);
ros::Rate loop_rate(1);
learn_topic::person man;
man.age = 0;
man.name = "allen";
ROS_INFO("pub: Here's a man named, how old is he? %s", man.name.data());
while (ros::ok()){
man.age++;
pub.publish(man);
loop_rate.sleep();
}
return 0;
}
subscriber
编辑cpp_listener.cpp文件:
#include <ros/ros.h>
#include "learn_topic/person.h"
void getAgeCallback(const learn_topic::personConstPtr &person){
ROS_INFO("answer: %s's age is %d", person->name.data(), person->age);
}
int main(int argc, char **argv){
ros::init(argc, argv, "cpp_listener");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("/person_topic",1, getAgeCallback);
ros::spin();
return 0;
}
运行两个node
首先在cmakelists文件中输入如下内容:
add_executable(cpptalker_node src/cpp_talker.cpp)
target_link_libraries(cpptalker_node
${catkin_LIBRARIES}
)
add_executable(cpplistener_node src/cpp_listener.cpp)
target_link_libraries(cpplistener_node
${catkin_LIBRARIES}
)
然后先在工作空间catkin_make
, 再在两个终端分别启动talker和listener节点:
rosrun learn_topic cpptalker_node
rosrun learn_topic cpplistener_node
终端输出对应结果:
查看node节点图,没有问题
4. 创建node实现message的发布与订阅(python版本)
ros同样提供了python接口,我们用python再将上述实验实现一次,方便大家学习用python实现node节点的时候应该注意的事项
首先,在scripts文件夹中新建两个文件,分别命名为python_talker.py和python_listener.py,之后的整个功能包文件目录如下:
publisher
#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from learn_topic.msg import person
def talker():
pub = rospy.Publisher('person_topic', person, queue_size=10)
rospy.init_node('pytalker', anonymous=True)
rate = rospy.Rate(1)
name='allen'
rospy.loginfo('Talker: this man named %s', name)
i = 0
while not rospy.is_shutdown():
pub.publish(person('allen',i))
i = i+1
rate.sleep()
if __name__ == '__main__':
talker()
subscriber
#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from learn_topic.msg import person
def callback(person):
rospy.loginfo('Listener: %s''s age is %d', person.name, person.age)
def listener():
rospy.init_node('pylistener', anonymous=True)
rospy.Subscriber('person_topic', person, callback)
rospy.spin()
if __name__ == '__main__':
listener()
运行俩个node节点
python是脚本语言不需要编译,运行前将scripts文件夹下的py文件都给执行权限:
sudo chmod +x *.py
然后分别运行两个node节点:
rosrun lea_topic python_talker.py
rosrun learn_topic python_listener.py
运行结果:
查看node节点图,没有问题
三、总结
关于topic通讯的学习,我们告一段落,基本能够涉及到的都已经覆盖,接下来,我们会进入第二章service通讯的学习。