用C++写简单的发布和订阅
1. 写发布节点
roscdbeginner_tutorials
1.1代码
在beginner_tutorials中创建一个src目录,并创建文件talker.cpp
代码地址:
https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp
创建目录:
mkdir -p src
代码内容:
27#include "ros/ros.h"
28#include "std_msgs/String.h"
29
30#include <sstream>
31
32/**
33 * This tutorial demonstrates simple sending of messages over the ROS system.
34 */
35intmain(intargc, char **argv)
36{
37 /**
38 * The ros::init() function needs to see argc and argv so that it can perform
39 * any ROS arguments and name remapping that were provided at the command line.
40 * For programmatic remappings you can use a different version of init() which takes
41 * remappings directly, but for most command-line programs, passing argc and argv is
42 * the easiest way to do it. The third argument to init() is the name of the node.
43 *
44 * You must call one of the versions of ros::init() before using any other
45 * part of the ROS system.
46 */
47 ros::init(argc, argv, "talker");
48
49 /**
50 * NodeHandle is the main access point to communications with the ROS system.
51 * The first NodeHandle constructed will fully initialize this node, and the last
52 * NodeHandle destructed will close down the node.
53 */
54 ros::NodeHandlen;
55
56 /**
57 * The advertise() function is how you tell ROS that you want to
58 * publish on a given topic name. This invokes a call to the ROS
59 * master node, which keeps a registry of who is publishing and who
60 * is subscribing. After this advertise() call is made, the master
61 * node will notify anyone who is trying to subscribe to this topic name,
62 * and they will in turn negotiate a peer-to-peer connection with this
63 * node. advertise() returns a Publisher object which allows you to
64 * publish messages on that topic through a call to publish(). Once
65 * all copies of the returned Publisher object are destroyed, the topic
66 * will be automatically unadvertised.
67 *
68 * The second parameter to advertise() is the size of the message queue
69 * used for publishing messages. If messages are published more quickly
70 * than we can send them, the number here specifies how many messages to
71 * buffer up before throwing some away.
72 */
73 ros::Publisherchatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
74
75 ros::Rateloop_rate(10);
76
77 /**
78 * A count of how many messages we have sent. This is used to create
79 * a unique string for each message.
80 */
81 intcount = 0;
82 while (ros::ok())
83 {
84 /**
85 * This is a message object. You stuff it with data, and then publish it.
86 */
87 std_msgs::Stringmsg;
88
89 std::stringstreamss;
90 ss << "hello world " << count;
91 msg.data = ss.str();
92
93 ROS_INFO("%s", msg.data.c_str());
94
95 /**
96 * The publish() function is how you send messages. The parameter
97 * is the message object. The type of this object must agree with the type
98 * given as a template parameter to the advertise<>() call, as was done
99 * in the constructor above.
100 */
101 chatter_pub.publish(msg);
102
103 ros::spinOnce();
104
105 loop_rate.sleep();
106 ++count;
107 }
108
109
110 return0;
111}
1.2代码解释
27#include "ros/ros.h"
这两行是包含ros的最常用的ROS头文件
28#include "std_msgs/String.h"
这包括在std_msgs包中的std_msgs / String消息。 这是从该包中的String.msg文件自动生成的头文件。 有关消息定义的更多信息,请参阅消息页面。
47 ros::init(argc, argv, "talker");
初始化ROS。 这使得ROS可以通过命令行重新命名 - 现在不重要。 这也是我们指定节点名称的地方。 节点名称在运行的系统中必须是唯一的。
这里使用的名称必须是基本名称,即它不能包含/。
54 ros::NodeHandlen;
创建一个句柄到这个进程的节点。 创建的第一个NodeHandle实际上将执行节点的初始化,而最后一个NodeHandle将清除节点正在使用的任何资源。
73 ros::Publisherchatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
告诉主节点,我们将要发布关于主题聊天的类型为std_msgs / String的消息。 这可以让主节点告诉任何正在监听的节点,我们将发布关于该主题的数据。 第二个参数是我们的发布队列的大小。 在这种情况下,如果我们发布速度过快,最多可能会缓存1000条消息,然后才开始丢弃旧消息。
NodeHandle ::advertise()返回一个ros:: Publisher对象,它有两个用途:1)它包含一个publish()方法,它允许你将消息发布到它创建的主题上; 2)当它超出范围 ,它会自动发布。
75 ros::Rateloop_rate(10);
ros :: Rate对象允许你指定一个你想循环的频率。 它会记录自上次调用Rate :: sleep()以来已经过了多长时间,并且在正确的时间内睡眠。
在这种情况下,我们告诉它我们要以10Hz运行。
81 intcount = 0;
82 while (ros::ok())
83 {
默认情况下,roscpp会安装一个SIGINT处理程序,它提供了Ctrl-C处理,如果发生这种情况,会导致ros :: ok()返回false。
ros :: ok()会返回false,如果:
接收到一个SIGINT(Ctrl-C)
我们已经被同名的另一个节点踢出了网络
ros :: shutdown()已被应用程序的另一部分调用。
所有ros :: NodeHandles都被销毁了
一旦ros:: ok()返回false,所有的ROS调用都将失败。
87 std_msgs::Stringmsg;
88
89 std::stringstreamss;
90 ss << "hello world " << count;
91 msg.data = ss.str();
我们使用消息适配类在ROS上广播消息,通常从msg文件生成。 更复杂的数据类型是可能的,但现在我们要使用标准的String消息,它有一个成员:“data”。
101 chatter_pub.publish(msg);
现在我们实际上是把消息广播给任何连接的节点。
93 ROS_INFO("%s", msg.data.c_str());
ROS_INFO和friends是我们的printf / cout的替代品。 有关更多信息,请参阅rosconsole文档。
103 ros::spinOnce();
在这个简单的程序中调用ros :: spinOnce()并不是必须的,因为我们没有收到任何回调。 但是,如果您要在此应用程序中添加订阅,并且在此处没有ros :: spinOnce(),则您的回调将永远不会被调用。 所以,为了好的习惯添加它。
105 loop_rate.sleep();
现在我们使用ros:: Rate对象来休眠剩下的时间,让我们达到10Hz的发布速率。
以下是正在发生的事情的简明版本:
初始化ROS系统
广告宣传我们将发布关于聊天主题的std_msgs / String消息
循环不断发布消息每秒钟10次
现在我们需要编写一个节点来接收这些消息。
简洁代码:
27#include "ros/ros.h"
28#include "std_msgs/String.h"
30#include <sstream>
35intmain(intargc, char **argv)
36{
47 ros::init(argc, argv, "talker");
54 ros::NodeHandlen;
73 ros::Publisherchatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
75 ros::Rateloop_rate(10);
81 intcount = 0;
82 while (ros::ok())
83 {
87 std_msgs::Stringmsg;
89 std::stringstreamss;
90 ss << "hello world " << count;
91 msg.data = ss.str();
93 ROS_INFO("%s", msg.data.c_str());
101 chatter_pub.publish(msg);
103 ros::spinOnce();
105 loop_rate.sleep();
106 ++count;
107 }
110 return0;
111}
2. 写一个订阅节点
在src下创建文件listener.cpp,代码地址:
https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/listener/listener.cpp
代码内容:
28#include "ros/ros.h"
29#include "std_msgs/String.h"
30
31/**
32 * This tutorial demonstrates simple receipt of messages over the ROS system.
33 */
34voidchatterCallback(conststd_msgs::String::ConstPtr& msg)
35{
36 ROS_INFO("I heard: [%s]", msg->data.c_str());
37}
38
39intmain(intargc, char **argv)
40{
41 /**
42 * The ros::init() function needs to see argc and argv so that it can perform
43 * any ROS arguments and name remapping that were provided at the command line.
44 * For programmatic remappings you can use a different version of init() which takes
45 * remappings directly, but for most command-line programs, passing argc and argv is
46 * the easiest way to do it. The third argument to init() is the name of the node.
47 *
48 * You must call one of the versions of ros::init() before using any other
49 * part of the ROS system.
50 */
51 ros::init(argc, argv, "listener");
52
53 /**
54 * NodeHandle is the main access point to communications with the ROS system.
55 * The first NodeHandle constructed will fully initialize this node, and the last
56 * NodeHandle destructed will close down the node.
57 */
58 ros::NodeHandlen;
59
60 /**
61 * The subscribe() call is how you tell ROS that you want to receive messages
62 * on a given topic. This invokes a call to the ROS
63 * master node, which keeps a registry of who is publishing and who
64 * is subscribing. Messages are passed to a callback function, here
65 * called chatterCallback. subscribe() returns a Subscriber object that you
66 * must hold on to until you want to unsubscribe. When all copies of the Subscriber
67 * object go out of scope, this callback will automatically be unsubscribed from
68 * this topic.
69 *
70 * The second parameter to the subscribe() function is the size of the message
71 * queue. If messages are arriving faster than they are being processed, this
72 * is the number of messages that will be buffered up before beginning to throw
73 * away the oldest ones.
74 */
75 ros::Subscribersub = n.subscribe("chatter", 1000, chatterCallback);
76
77 /**
78 * ros::spin() will enter a loop, pumping callbacks. With this version, all
79 * callbacks will be called from within this thread (the main one). ros::spin()
80 * will exit when Ctrl-C is pressed, or the node is shutdown by the master.
81 */
82 ros::spin();
83
84 return0;
85}
2.2代码解释
现在,让我们一块一块地分解它,忽略上面已经解释过的一些内容:
34voidchatterCallback(conststd_msgs::String::ConstPtr& msg)
35{
36 ROS_INFO("I heard: [%s]", msg->data.c_str());
37}
这是在新消息到达聊天话题时将被调用的回调函数。 这个消息是通过一个boost shared_ptr传递的,这意味着如果你愿意的话,你可以把它存储起来,而不用担心它被删除,而且不需要复制底层的数据。
75 ros::Subscribersub = n.subscribe("chatter", 1000, chatterCallback);
连续不断的订阅主节点topic。 每当新消息到达,ROS都会调用chatterCallback()函数。 第二个参数是队列大小,以防止我们不能够足够快地处理消息。 在这种情况下,如果队列达到1000条消息,我们将在新消息到达时开始丢弃旧消息。
NodeHandle :: subscribe()返回一个ros :: Subscriber对象,你必须坚持直到你想取消订阅。 当订阅者对象被破坏时,它将自动取消订阅聊天主题。
NodeHandle :: subscribe()函数的版本允许您指定一个类成员函数,甚至可以由Boost.Function对象调用的任何东西。 roscpp概述包含更多信息。
82 ros::spin();
ros :: spin()进入一个循环,尽可能快地调用消息回调。 不要担心,如果没有什么可做的话,它不会占用太多的CPU。 ros:: spin()将会退出,一旦ros :: ok()返回false,这意味着ros :: shutdown()已经被默认的Ctrl-C处理程序调用,主控告诉我们关闭,或者被调用手动。
还有其他的方式来回调,但我们不会担心这里的人。 roscpp_tutorials包有一些演示应用程序来演示这个。 roscpp概述还包含更多信息。
现在再来看一下这个程序的执行流程:
初始化ROS系统
订阅连续发布的topic
旋转,等待消息到达
当消息到达时,调用chatterCallback()函数
3. 重新编译节点
你在之前的教程中使用了catkin_create_pkg,它为你创建了一个package.xml和一个CMakeLists.txt文件。
生成的CMakeLists.txt看起来应该是这样的(修改了创建Msgs和Srvs教程以及删除了未使用的注释和示例):(代码地址)
代码内容:
1cmake_minimum_required(VERSION 2.8.3)
2project(beginner_tutorials)
3
4## Find catkin and any catkin packages
5find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
6
7## Declare ROS messages and services
8add_message_files(DIRECTORY msg FILES Num.msg)
9add_service_files(DIRECTORY srv FILES AddTwoInts.srv)
10
11## Generate added messages and services
12generate_messages(DEPENDENCIES std_msgs)
13
14## Declare a catkin package
15catkin_package()
不要担心修改注释(#)的例子,只需将这几行添加到CMakeLists.txt的底部:
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)
生成的CMakeLists.txt文件应该如下所示:(代码地址:
1cmake_minimum_required(VERSION 2.8.3)
2project(beginner_tutorials)
3
4## Find catkin and any catkin packages
5find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
6
7## Declare ROS messages and services
8add_message_files(FILES Num.msg)
9add_service_files(FILES AddTwoInts.srv)
10
11## Generate added messages and services
12generate_messages(DEPENDENCIES std_msgs)
13
14## Declare a catkin package
15catkin_package()
16
17## Build talker and listener
18include_directories(include ${catkin_INCLUDE_DIRS})
19
20add_executable(talker src/talker.cpp)
21target_link_libraries(talker ${catkin_LIBRARIES})
22add_dependencies(talker beginner_tutorials_generate_messages_cpp)
23
24add_executable(listener src/listener.cpp)
25target_link_libraries(listener ${catkin_LIBRARIES})
26add_dependencies(listener beginner_tutorials_generate_messages_cpp)
这将创建两个可执行文件,talker和listener,默认情况下它们将进入到devel空间的package目录中,默认位于〜/ catkin_ws / devel /lib / <package name>。
请注意,您必须将可执行目标的依赖项添加到消息生成目标:
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
这确保了这个包的消息头在使用之前被生成。如果使用来自catkin工作区内其他软件包的消息,则还需要将依赖关系添加到其各自的生成目标,因为catkin并行地生成所有项目。从* Groovy *开始,您可以使用以下变量来取决于所有必要的目标:
target_link_libraries(talker ${catkin_LIBRARIES})
您可以直接调用可执行文件,也可以使用rosrun来调用它们。 它们不会放在'<前缀> / bin'中,因为在将软件包安装到系统时会污染PATH。如果希望安装时将可执行文件放在PATH上,可以设置安装目标,请参阅:catkin / CMakeLists.txt
有关CMakeLists.txt文件的更多详细信息,请参阅:catkin / CMakeLists.txt
现在运行catkin_make:
# In your catkin workspace
$ cd ~/catkin_ws
$ catkin_make
注意:或者,如果您添加为新的pkg,则可能需要通过--force-cmake选项告诉catkin强制生成。Seecatkin/教程/using_a_workspace#With_catkin_make。
(这个地方直接在CMakeLists.txt后面添加那些代码之后编译出错,后来将CMakeLists.txt内容全部替换为官网内容编译成功。)
现在你已经写了一个简单的发布者和订阅者,让我们来看看简单的发布者和订阅者。
4. 运行发布和订阅
运行发布:
# In your catkin workspace
$ roscore
$ cd ~/catkin_ws
$ source./devel/setup.bash
$ rosrunbeginner_tutorials talker
运行订阅:
$ rosrunbeginner_tutorials listener