ROS学习之路03:编写基于话题(topic)进行通信的节点
2018-03-13 23:04
309 查看
关键术语:
节点(node):在ROS编程中,节点就是一个通过调用ROS客户端库(e.g.roscpp,
rospy,
roslisp)的API执行计算的进程,一个节点可以通过ROS的话题、服务和动作与其他的节点进行通信. 一个机器人可以包含很多个节点,例如:一个节点处理相机的图像,一个节点处理来自机器人的串行数据,一个节点用来计算里程计信息. 另外,使用节点可以使机器人系统具有容错能力,因为即使一个节点挂掉了,其他的节点依然可以正常运行的话,整个机器人系统仍然是可以工作的。机器人使用节点的另外一个好处就是降低了复杂度,也便于调试,因为一个节点只负责一项功能.
发布者(publisher):发布者是在基于话题(topic)的通信中只负责进行消息的发送的节点(node),发布者节点要想通过话题发送消息,需要先在节点管理器(master)那里进行相关信息的注册(registration).
订阅者(subscriber):订阅者是在基于话题的通信中只负责进行消息的接收的节点(node),订阅者节点要想通过话题订阅消息,也需要先在节点管理器(master)那里进行相关信息的注册(registration).
节点管理器(master):节点管理器负责基于话题的通信中发布者节点和订阅者节点进行通信前的注册,以便发布者节点和订阅者节点在实现基于话题通信前进行准确的连接.
话题(topic):话题是一种被命名的单项通信的总线,话题的名称具有唯一性.
消息(.msg):消息可以理解为一个数据块,数据块包含.msg消息文件中定义的基本数据
e.g. 定义机器人末端执行器空间位置的消息可以为:
float x float y float z
发布(publish):发布是发布者节点通过话题发布消息的过程.
订阅(subscribe):订阅是订阅者节点通过同一个话题话题订阅来自发布者节点发布的消息
创建ROS工作空间(workspace)
打开终端(快捷键:Ctrl + Alt + T),输入如下的命令流:mkdir -p ~/catkin_ws/src source /opt/ros/kinetic/setup.bash cd ~/catkin_ws/src catkin_init_workspace cd .. catkin_make
mkdir -p ~/catkin_ws/src:参数 -p 的作用是一次性创建catkin_ws目录及其子目录src
source /opt/ros/kinetic/setup.bash:目的是获取ROS的功能,如果你在~/.bashrc文件末尾添加了这一行的话,这一条命令就不需要了
catkin_init_workspace:初始化一个新的 catkin 工作空间,这一步可以不需要,因为后面的 catkin_make 顺便做了这个工作
cd ..:返回父目录,也就是 ~/catkin_ws
catkin_make:构建整个 catkin_ws 工作空间,该命令会在catkin_ws工作空间目录下分别创建build和devel目录,在devel目录下存放着各种 setup 文件,比如说我们前面用到的setup.bash,source 该文件就会把我们创建的工作空间添加进ROS环境变量. 如果你不想每次都要手动source该工作空间,可以把
source ~/catkin_ws/devel/setup.bash写入~/.bashrc文件中,并在终端中
source .bashrc即可.
在工作空间(catkin_ws)中创建功能包(package)
打开终端(快捷键:Ctrl + Alt + T),输入如下的命令流:cd ~/catkin_ws/src catkin_create_pkg basic_topic roscpp std_msgs
这里我们来分析一下创建功能包的命令:
catkin_create_pkg package_name [dependency1] [dependency2]:package_name是我们想要创建的功能包的名称,后面都是package_name所依赖的功能包.
注意:不管是我们自己创建的功能包还是直接git clone下来的功能包或者元功能包,必须放置在工作空间的src目录下才能被准确识别和构建.
roscpp:这个是ROS的C++实现,这是一个ROS的客户端库,它为C++程序员提供了一系列用于创建ROS节点的API,这里我们包含了roscpp,因为我们要创建一个基于C++的ROS节点,任何使用C++创建ROS节点的功能包必须包含roscpp这个依赖包
std_msgs:这个功能包包含了基本的ROS原始数据类型,例如:integer, float, string, array等. 我们可以通过包含该依赖包来直接在我们创建的节点中使用这些数据类型,而不用定义新的ROS数据类型.
创建完功能包之后,额外的依赖包可以通过手动的方式在CMakeLists.txt文件和package.xml文件中添加,这两个文件是通过
catkin_create_pkg命令创建功能包的时候在功能包中自动创建的
创建完功能包之后,我们可以直接调用
catkin_make命令来构建我们刚才创建的功能包,注意:这是一个空的功能包,因为我们还没有添加任何的节点.
构建成功之后,我们就可以向功能包的src目录添加节点了.
向功能包添加发布者(publisher)节点源文件
在新建的功能包中创建发布者节点的源码:topic_publisher.cpp
cd ~/catkin_ws source devel/setup.bash roscd basic_topic mkdir src && cd src gedit topic_publisher.cpp
在
topic_publisher.cpp中输入如下的内容:
#include "ros/ros.h" #include "std_msgs/String.h" int main(int argc, char **argv) { ros::init(argc, argv, "topic_publisher"); ros::NodeHandle nh; ros::Publisher topic_pub = nh.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); while (ros::ok()) { std_msgs::String msg; msg.data = "hello world"; topic_pub.publish(msg); loop_rate.sleep(); } return 0; }
#include "ros/ros.h":所有的基于roscpp客户端库编写的节点都必须包含该头文件
#include "std_msgs/String.h":由于发布者节点要发布std_msgs::String消息,因此要包含定义该消息的头文件,这是一个标准消息头文件,我们不需要自己定义,直接调用即可.
ros::init(argc, argv, "topic_publisher");:通过给定的名称
topic_publisher来初始化一个节点,需要注意的是节点的名称必须是唯一的,所有基于c++的ROS节点在主函数的开头都必须要有这一条初始化语句.
ros::NodeHandle nh;:创建一个NodeHan
4000
dle对象,它作用是用来和ROS系统进行通信的.
ros::Publisher topic_pub = nh.advertise<std_msgs::String>("chatter", 1000);:创建话题的发布者,对话题进行命名,将消息类型与话题进行绑定,定义缓冲区的大小(也就是排队等待传递给消息订阅者的最大消息数量,如果数量超过这个值,最先添加进这个消息队列的消息会被清理掉)
ros::Rate loop_rate(10);:创建了一个ros::Rate对象,目的是控制消息发布的频率,如果频率过快,发布者
发布(publish)的消息数据就会很快填满消息队列(也就是缓冲区),而将消息
发送给订阅者节点这个通信过程是需要时间的.
注意:基于话题的通信中,这里我们所讲的发布和发送不是一个概念,发布者发布的数据是存储在消息队列中的,这个消息发布的频率可以通过ros::Rate对象控制,而将消息队列中的消息数据发送给具有相同话题的订阅者节点这一消息发送的过程是由后台一个单独的线程来完成的.
注意:消息队列中的最大数量是可以容纳的消息最大个数,而不是容纳消息的最大字节数.
如果消息发布的频率比较快,那么缓冲区就需要设置大一点.
ros::ok():这个函数会检查我们的程序作为节点是否仍然出于运行良好的状态,如果节点运行正常,它会返回true,如果节点因为某种原因停止工作(e.g. 在运行节点的终端按下Ctrl + C),它会返回false.
std_msgs::String msg;:定义了待发布的消息对象
msg.data = "hello world";:填充消息对象中的字段
topic_pub.publish(msg);:发布者发布消息到消息缓冲区,等待后台单独的线程将缓冲区中的消息发送给具有相同话题的订阅者节点.
loop_rate.sleep();:每次调用此方法就会在程序中产生延迟,延迟的时间用来阻止循环迭代的速率超过指定的速率,一般我们在一次循环的末尾调用此方法以消耗掉目标循环迭代周期与实际循环迭代周期之间的时间差.
向功能包添加订阅者(subscriber) 节点源文件
#include "ros/ros.h" #include "std_msgs/String.h" void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); int main(int argc, char **argv) { ros::init(argc, argv, "topic_subscriber"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback); ros::spin(); return 0; }
#include "ros/ros.h":c++节点必须包含roscpp客户端头文件
#include "std_msgs/String.h":订阅者节点要订阅std_msgs::String类型的消息对象,因此需要包含该类型消息的头文件
void chatterCallback(const std_msgs::String::ConstPtr& msg):这里,回调函数名 = topic_name + Callback, 回调函数的参数是:引用消息对象的常量指针
ROS_INFO("I heard: [%s]", msg->data.c_str());:回调函数中,msg是引用自消息对象的常量指针,因此需要通过msg->data的方式调用消息对象中的data字段,由于data是string字段类型,在ROS_INFO这种c语言格式化输出中需要将string类型的变量转换成char*类型输出.
ros::init(argc, argv, "topic_subscriber");:通过给定的名称来初始化一个节点,节点名称必须是唯一的,所有基于c++的ROS节点在主函数的开头都必须要有这一条初始化语句
ros::NodeHandle nh;:创建NodeHandle对象
ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);:创建订阅者,给出要订阅的话题名称,给定订阅缓冲区的大小(也就是订阅消息队列中同一时刻可以存储的消息数量的最大值,如果新的消息到达时队列已满,那么最早到达的还没有被回调函数处理的消息将被丢弃以便腾出空间存储新到来的消息),回调函数的指针(其实就是回调函数的名称),回调函数的作用就是处理订阅缓冲区中接收到的消息对象
ROS清空一个发布者节点的消息队列的速率取决于实际上把消息传输给订阅者节点所用的时间,而这个时间很大程度上是不受控制的,因为消息传输的快慢与传输环境有很大的关系,而ROS清空订阅者节点的消息队列的速率取决于我们调用并执行回调函数的快慢. 为此,我们需要使用ros::spin()或者ros::spinOnce()函数来确保回调函数的执行.
ros::spin();:订阅者节点程序执行到这里的时候就会进入一个事件循环,不断地执行回调函数来处理订阅者接收到并存储在订阅者消息缓冲区中的消息对象. 与之类似的是ros::spinOnce()函数,该函数会执行一次回调函数,执行完成后会返回主函数执行后面的代码,这种方法可以严格控制处理消息对象的速度.
修改CMakeLists.txt文件
为了构建我们功能包,编译我们添加的节点源文件,我们需要修改创建功能包时自动创建的CMakeLists.txt文件cmake_minimum_required(VERSION 2.8.3) project(basic_topic) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs ) catkin_package( CATKIN_DEPENDS roscpp std_msgs ) include_directories( ${catkin_INCLUDE_DIRS} ) add_executable(topic_publisher src/topic_publisher.cpp) add_executable(topic_subscriber src/topic_subscriber.cpp) target_link_libraries(topic_publisher ${catkin_LIBRARIES}) target_link_libraries(topic_publisher ${catkin_LIBRARIES})
构建功能包,将节点源文件编译成可执行的节点程序
cd ~/catkin_ws catkin_make
运行节点
第一步:打开节点管理器、参数服务器和日志节点打开终端,输入如下的命令:
roscore
第二步:运行发布者节点
打开终端,将工作空间添加到ROS环境,然后运行发布者节点:
cd ~/catkin_ws rsource devel/setup.bash rosrun basic_topic topic_publisher
第三步:运行订阅者节点
打开终端,将工作空间添加到ROS环境,然后运行订阅者节点:
cd ~/catkin_ws source devel/setup.bash rosrun basic_topic topic_subscriber
至此,基于话题的消息发布和订阅的内容就讲完了.
Enjoy It!
相关文章推荐
- ROS学习之路06:编写基于自定义动作(.action)进行通信的节点
- 【源码】c#编写的安卓客户端与Windows服务器程序进行网络通信
- 编写高质量OC代码52建议总结:23.通过委托与数据源协议进行对象间通信
- 树莓派与Arduino Leonardo使用NRF24L01无线模块通信之基于RF24库 (五) 树莓派单子节点发送数据
- 基于SearchControl控件对节点进行查询的操作
- 基于API的MFC串口通信程序编写
- 本篇主要讲解在未使用其他框架(Spring)整合情况下,独立基于ActiveMQ,使用JMS规范进行消息通信。
- Go语言基于Socket编写服务器端与客户端通信的实例
- socket 网络编程快速入门(一)教你编写基于UDP/TCP的服务(客户端)通信
- c#编写的基于TCP通信的微风IM 版本3 新年新UI
- 对xml节点进行修改和编写
- 在CB6下基于api函数编写串口通信程序简介
- 出现“主机无法与已启用 Virtual SAN 的群集中的所有其他节点进行通信”错误
- 【源码】c#编写的安卓客户端与Windows服务器程序进行网络通信
- c#编写的基于Socket的异步通信系统--SanNiuSignal.DLL已开源
- 基于select模型的tcp服务器------一个服务器如何与多个客户端进行通信?
- 基于多台linux主机通过1台服务器进行socket通讯小程序编写
- Linux平台基于C编写的文本通信平台
- c#编写的基于Socket的异步通信系统
- 【源码】c#编写的安卓客户端与Windows服务器程序进行网络通信