机器人操作系统ROS学习实战篇之------消息发布器和订阅器
上一篇研究了ubuntu系统下ROS环境的安装与配置,接下来进入真正的实战,开始撸码,按照惯例一般都是从hello wrold开始。
在写代码之前我们先熟悉一下ROS中的几个常用概念:
1、Node(节点):执行一些运算任务的进程,数据传输和处理的程序,可以理解为C++或者Python程序主函数所在的程序。
2、Message(消息):节点之间是通过传送消息进行通讯的,每一个消息都是一个严格的数据结构。即支持标准的数据类型(整型,浮点型,布尔型等等)和原始数组类型。也可以包含任意嵌套的结构和数组(类似于C语言的结构structs)。比如GPS采集到位置消息,温度计采集到的温度等,任何数据都能作为message。
3、Topic(主题):消息是以一种发布/订阅的方式进行传递的。一个节点可以在一个给定的主题中发布消息。另外的节点针对某个主题关注与订阅特定类型的数据。可以同时有多个节点发布或者订阅同一个主题的消息。
4、Service(服务):虽然基于话题的发布/订阅模型是很灵活的通讯模式,但是它广播式的路径规划对于可以简化节点设计的同步传输模式并不适合。在ROS中,我们称之为一个服务,用一个字符串和一对严格规范的消息定义:一个用于请求,一个用于回应。这类似于web服务器,web服务器是由URIs定义的,同时带有完整定义类型的请求和回复文档。需要注意的是,不像话题,只有一个节点可以以任意独有的名字广播一个服务:只有一个服务可以称之为“分类象征”,比如说,任意一个给出的URI地址只能有一个web服务器。
5、Discovery(发现):ROS2里是没有master的,那么各节点之间是怎么知道彼此存在的呢?这就靠一种自动发现的机制——Discovery。什么意思呢?简单来说,当一个节点启动后,首先在网络中发条广播,大声告诉这个世界我来了,其他节点听到之后,也纷纷反馈各自的信息,这样一来二去也就联系上了。当然,万一哪个节点掉线了怎们办?没关系,每个节点都会周期性的发布广播,告诉其他节点他还在线,就算要下线,他也会广播告诉其他节点他要走了,下次再聊。总之,发现(Discovery)可以理解为一种身份声明和响应的机制。
6、Package(包):ROS的软件以包的方式组织起来。包包含节点、ROS依赖库、数据套、配置文件、第三方软件、或者任何其他逻辑构成。包的目标是提供一种易于使用的结构以便于软件的重复使用。
7、Stack(堆):堆是包的集合,它提供一个完整的功能,像“navigation stack”。Stack与版本号关联,同时也是如何发行ROS软件方式的关键。
ROS是一种分布式处理框架。这使可执行文件能被单独设计,并且在运行时松散耦合。这些过程可以封装到包(Packages)和堆(Stacks)中,以便于共享和分发。
第一个ROS程序
以下代码采用官网的例子,官网地址
1:创建workspace,打开一个terminal, 依次输入下面命令:
1 source /opt/ros/melodic/setup.bash #进入ros环境,我这里是melodic版本,如果使用其他版本就把这个名称替换掉 2 mkdir -p ~/catkin_ws/src #创建ros工作空间,我这里按照官网叫catkin_ws,这个空间的名字可以随便取,但是src目录必须得有,因为是用来存放源程序的
3 cd ~/catkin_ws/ #进入ros工作空间
4 catkin_make #进行编译,编译成功的话会发现多了build和devel两个文件夹
建立好工作空间之后开始创建第一个ros项目,继续在terminal中输入下面的命令:
1 cd ~/catkin_ws/src #进入src目录 2 catkin_create_pkg pub_sub_test std_msgs rospy roscpp #执行ros命令,创建消息包 3 cd .. #进入工作空间 4 catkin_make #进行编译
2:发布消息程序的代码(发布器),继续在terminal中输入下面的命令:
1 cd ~/catkin_ws/src/pub_sub_test/src #进入src文件夹 2 touch pub_string.cpp #创建一个pub_string的c++文件
3:打开pub_string.cpp文件,里面的内容如下:
1 #include "ros/ros.h" 2 #include "std_msgs/String.h" 3 4 #include <sstream> 5 6 /** 7 * This tutorial demonstrates simple sending of messages over the ROS system. 8 */ 9 int main(int argc, char **argv) 10 { 11 /** 12 * The ros::init() function needs to see argc and argv so that it can perform 13 * any ROS arguments and name remapping that were provided at the command line. For programmatic 14 * remappings you can use a different version of init() which takes remappings 15 * directly, but for most command-line programs, passing argc and argv is the easiest 16 * way to do it. The third argument to init() is the name of the node. 17 * 18 * You must call one of the versions of ros::init() before using any other 19 * part of the ROS system. 20 */ 21 ros::init(argc, argv, "talker"); 22 23 /** 24 * NodeHandle is the main access point to communications with the ROS system. 25 * The first NodeHandle constructed will fully initialize this node, and the last 26 * NodeHandle destructed will close down the node. 27 */ 28 ros::NodeHandle n; 29 30 /** 31 * The advertise() function is how you tell ROS that you want to 32 * publish on a given topic name. This invokes a call to the ROS 33 * master node, which keeps a registry of who is publishing and who 34 * is subscribing. After this advertise() call is made, the master 35 * node will notify anyone who is trying to subscribe to this topic name, 36 * and they will in turn negotiate a peer-to-peer connection with this 37 * node. advertise() returns a Publisher object which allows you to 38 * publish messages on that topic through a call to publish(). Once 39 * all copies of the returned Publisher object are destroyed, the topic 40 * will be automatically unadvertised. 41 * 42 * The second parameter to advertise() is the size of the message queue 43 * used for publishing messages. If messages are published more quickly 44 * than we can send them, the number here specifies how many messages to 45 * buffer up before throwing some away. 46 */ 47 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); 48 49 ros::Rate loop_rate(10); 50 51 /** 52 * A count of how many messages we have sent. This is used to create 53 * a unique string for each message. 54 */ 55 int count = 0; 56 while (ros::ok()) 57 { 58 /** 59 * This is a message object. You stuff it with data, and then publish it. 60 */ 61 std_msgs::String msg; 62 63 std::stringstream ss; 64 ss << "hello world " << count; 65 msg.data = ss.str(); 66 67 ROS_INFO("%s", msg.data.c_str()); 68 69 /** 70 * The publish() function is how you send messages. The parameter 71 * is the message object. The type of this object must agree with the type 72 * given as a template parameter to the advertise<>() call, as was done 73 * in the constructor above. 74 */ 75 chatter_pub.publish(msg); 76 77 ros::spinOnce(); 78 79 loop_rate.sleep(); 80 ++count; 81 } 82 83 84 return 0; 85 }
以上代码简单来说就是循环发布hello world消息,直到按下Ctrl+C退出程序,具体可以看里面的官方注释。
4:接收消息的程序(接收器),上面完成了发布器,接下来开始写接收器。打开terminal,继续输入下面的命令:
1 cd ~/catkin_ws/src/pub_sub_test/src #进入src文件夹 2 touch sub_string.cpp #创建一个sub_string的c++文件
打开sub_string.cpp我们把下面的代码复制进去
1 #include "ros/ros.h" 2 #include "std_msgs/String.h" 3 4 /** 5 * This tutorial demonstrates simple receipt of messages over the ROS system. 6 */ 7 void chatterCallback(const std_msgs::String::ConstPtr& msg) 8 { 9 ROS_INFO("I heard: [%s]", msg->data.c_str()); 10 } 11 12 int main(int argc, char **argv) 13 { 14 /** 15 * The ros::init() function needs to see argc and argv so that it can perform 16 * any ROS arguments and name remapping that were provided at the command line. For programmatic 17 * remappings you can use a different version of init() which takes remappings 18 * directly, but for most command-line programs, passing argc and argv is the easiest 19 * way to do it. The third argument to init() is the name of the node. 20 * 21 * You must call one of the versions of ros::init() before using any other 22 * part of the ROS system. 23 */ 24 ros::init(argc, argv, "listener"); 25 26 /** 27 * NodeHandle is the main access point to communications with the ROS system. 28 * The first NodeHandle constructed will fully initialize this node, and the last 29 * NodeHandle destructed will close down the node. 30 */ 31 ros::NodeHandle n; 32 33 /** 34 * The subscribe() call is how you tell ROS that you want to receive messages 35 * on a given topic. This invokes a call to the ROS 36 * master node, which keeps a registry of who is publishing and who 37 * is subscribing. Messages are passed to a callback function, here 38 * called chatterCallback. subscribe() returns a Subscriber object that you 39 * must hold on to until you want to unsubscribe. When all copies of the Subscriber 40 * object go out of scope, this callback will automatically be unsubscribed from 41 * this topic. 42 * 43 * The second parameter to the subscribe() function is the size of the message 44 * queue. If messages are arriving faster than they are being processed, this 45 * is the number of messages that will be buffered up before beginning to throw 46 * away the oldest ones. 47 */ 48 ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); 49 50 /** 51 * ros::spin() will enter a loop, pumping callbacks. With this version, all 52 * callbacks will be called from within this thread (the main one). ros::spin() 53 * will exit when Ctrl-C is pressed, or the node is shutdown by the master. 54 */ 55 ros::spin(); 56 57 return 0; 58 }
以上代码简单来说就是接收发布器发布的hello world消息,并打印在终端上,具体可以看里面的官方注释。
5:编译ros程序,打开terminal,继续输入下面的命令:
1 cd ~/catkin_ws/src/pub_sub_test/ 2 gedit CMakeLists.txt
用gedit打开CMakeLists.txt文件,然后找到##Declare a C++ executable,在这一行的前面或者后面添加如下内容:
1 add_executable(pub_string src/pub_string.cpp) 2 target_link_libraries(pub_string ${catkin_LIBRARIES}) 3 add_executable(sub_string src/sub_string.cpp) 4 target_link_libraries(sub_string ${catkin_LIBRARIES})
添加完以上内容后我们保存并退出CMakeLists.txt文件。然后在terminal中继续输入如下命令:
1 cd ~/catkin_ws/ 2 catkin_make
不出意外的话编译成功,接下来就可以执行你的第一个ROS demo了。
6:执行ROS程序
一切顺利的话接下来就是见证奇迹的时候了,我们先打开三个terminal,在第一个terminal中输入如下命令:
1 roscore
第二个terminal中输入如下命令:
1 cd ~/catkin_ws/ 2 source devel/setup.bash 3 rosrun pub_sub_test sub_string
第三个terminal中输入如下命令:
cd ~/catkin_ws/ source devel/setup.bash rosrun pub_sub_test pub_string
这个时候应该会在第二个和第三个terminal中看到如下内容,其中左边的是消息发布器,右边的是消息接收器,两个都是循环的,如果要退出的话按下Ctr+C即可。
在退出之前我们可以再打开一个terminal,输入下面的内容:
1 source ~/catkin_ws/devel/setup.bash 2 rqt_graph
这时应该可以看到如下图片:
talker
就是发布信息的程序中取的节点的名字,listener
就是接收信息的程序中取的节点的名字,chatter
就是你在程序中取的topic的名字.这副图传递的信息是节点
talker
通过topicchatter
向节点listener
发布消息rqt_graph
命令简洁地表现除了node和node之间的关系,当以后有很多节点、很多topic时,可以通过查看这个图像理清它们之间的关系。至此ROS的消息发布器和订阅器的demo已经完成。