写一个简单的发布器与订阅器(C++)(rawmeat:http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber(c%2B%2B))
注意:本教程翻译于2018.9.25,英文原版教程来自于wiki.ros.org,,内容有可能更新,请以英文原版教程为准。
描述:这个教程包括如何用C++写一个发布器与订阅器
教程等级:初学者
下一个教程:检查简单的发布器与订阅器
内容 (只介绍catkin版本)
1.写一个发布器节点
1.节点
2.节点解释()
2.写一个订阅器节点
1.节点
2.节点解释
3.构建你的节点
4.额外的资源
1.视频教程
1.写发布器节点
”节点“是ROS术语,可执行的,连接到ROS网络。这儿我们将会创造一个发布器(”谈论者“)节点,将会持续地广播消息。
改变目录到你之前教程catkin workspace创建的beginner_tutorials:
roscd beginner_tutorials
1.1 代码
在beginner_tutorials package目录中创造一个src目录:
mkdir -p src
这个目录将会为我们的beginner_tutorials package包含任何源文件。
在beginner_tutorials package内创造src/talker.cpp然后黏贴以下的内容进入:
https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp
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 */
35 int main(int argc, 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::NodeHandle n;
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::Publisher chatter_pub = n.advertise<std_msgs::String>(“chatter”, 1000);
74
75 ros::Rate loop_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 int count = 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::String msg;
88
89 std::stringstream ss;
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 return 0;
111 }
1.2 代码解释
现在,我们分解代码:
27 #include “ros/ros.h”
28
ros/ros.h是一个便利集合,包含了使用ROS系统的最常用的公共部分所有header。
Toggle line numbers
28 #include “std_msgs/String.h”
29
这个包括了std_msgs/String消息,存在于 std_msgs package中。这个header从那个package中的String.msg文件自动产生的。为了更多消息定义的信息,请见msg页面。
47 ros::init(argc, argv, “talker”);
初始化ROS。这个使得ROS可以通过命令行进行命名重映射–现在还不重要。这个也是我们指定节点名称的地方。在一个运行的系统中节点名称必须是独特的。
在这儿使用的名称必须是一个基本名称,也就是说,不能有/在其中。
54 ros::NodeHandle n;
向这个进程节点创造一个handle。第一个被创造的nodehandle将会初始化节点,最后一个被毁灭的将会清除节点正在使用的任何资源。
73 ros::Publisher chatter_pub = n.advertise<std_msgs::String>(“chatter”, 1000);
告诉节点管理器我们将会发布一条std_msgs/String类型的消息在话题chatter。这个让主人告诉任何正在收听chatter的节点我们将会在话题发布数据。第二个参数是我们发布队列的尺寸,这种情况下如果我们发布太快,它将会在丢弃旧消息之前缓冲最多1000条消息。
NodeHandle::advertise()返回了一个ros::Publisher对象,它实现了两个目的:(1)它包含一个publish()方式,让你在话题上发布消息(2)当它超出范围时,它将会自动unadvertise。
75 ros::Rate loop_rate(10);
一个ros::Rate目标让你可以指定一个你愿意循环的频率。它将会跟踪自从上次对Rate::sleep()的召唤之后有多久,然后休眠正确的时间。
在这样的情况下我们将会以10Hz运行。
81 int count = 0;
82 while (ros::ok())
83 {
通过默认的roscpp将会安装一个SIGINT handler,提供ctrl-c handling,这将会造成ros::ok()返回错误值,如果发生的话。
Ros::ok()将会返回错误值,如果:
- 一个SIGINT被接收到(Ctrl-C)
- 我们已经被另外一个有着相同名字的节点踢下网络
- ros::shutdown()被应用的另外一部分召唤
- 所有的ros::NodeHandles被摧毁
一旦ros::ok()返回错误值,所有的ROS召唤将会失败。
87 std_msgs::String msg;
88
89 std::stringstream ss;
90 ss << "hello world " << count;
91 msg.data = ss.str();
我们在ROS使用消息适应类广播消息,通常由一个msg文件产生。更复杂的数据类型也可以,但是现在我们只是使用标准的String 消息,有一个成员:”数据”。
101 chatter_pub.publish(msg);
现在我们实际上向任何连接的人广播消息。
93 ROS_INFO("%s", msg.data.c_str());
ROS_INFO和friends是我们对printf/cout的替代。可以看rosconsole documentation更多消息。
103 ros::spinOnce();
这儿召唤ros::spinOnce()对于这个简单的程序不是必需的,因为我们没有收到任何回复。然而,如果你在这个应用中增加订阅,但是在这儿没有ros::spinOnce(),你的回复将会永远不会被召唤。所以加上这个是一个好做法。
105 loop_rate.sleep();
现在我们使用ros::Rate目标来休眠一段剩下的时间,让我们满足10Hz的发布率。
这儿是正在进行的浓缩版本:
- 初始化ROS系统
- 通知我们将会在chatter话题发布std_msgs/String消息给主人
- 循环,发布消息给chatter,10次一秒
现在我们需要写节点来接收消息。
2.写订阅器节点
2.1代码
在beginner_tutorials package中创造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 * /
34 void chatterCallback(const std_msgs::String::ConstPtr& msg)
35 {
36 ROS_INFO(“I heard: [%s]”, msg->data.c_str());
37 }
38
39 int main(int argc, 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::NodeHandle n;
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::Subscriber sub = 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 return 0;
85 }
2.2代码解释
现在,让我们分解成一片一片的,忽略上文已经解释过的。
34 void chatterCallback(const std_msgs::String::ConstPtr& msg)
35 {
36 ROS_INFO(“I heard: [%s]”, msg->data.c_str());
37 }
这个是回调函数,将会在一条新的消息到达chatter话题的时候被召唤。消息在一个boost shared_ptr中被传递,这意味着你可以储存起来如果你愿意,而不用担心它没有复制潜在数据就被删除。
75 ros::Subscriber sub = n.subscribe(“chatter”, 1000, chatterCallback);
用节点管理器订阅Chatter话题。只要有消息到达,ROS将会召唤chatterCallback()函数。第二个参数是队列尺寸,防止我们不能快速处理数据。这种情况下,如果队列达到了1000条消息,我们将会在新信息到达时丢弃旧信息。
NodeHandle::subscribe()返回一个ros::Subscriber对象,你必须坚持下去,直到想退订为止。当订阅者被摧毁,它将会从chatter话题自动地退订。
这儿有NodeHandle::subscribe()函数的版本,让你可以指定类成员函数,或者甚至任何可以被一个Boost函数对象召唤的。roscpp概述包含更多消息。
82 ros::spin();
ros::spin()进入一个循环,尽可能快地调用消息的回调。如果没有任何事情要做,不用担心,它不会占用很多CPU。一旦ros::ok()返回false,则ros::spin()将退出,这意味着ros::shutdown()已被调用,或者是由默认的Ctrl-C处理程序调用,节点管理器告诉我们要关闭,或者被手动调用。
还有其他方法来回调,但我们不担心这些。roscpp_tutorialspackage有一些演示应用程序来演示这一点。roscpp 概述还包含更多信息。
又一次,这有一个正在运行的浓缩的版本:
- 初始化ROS系统
- 订阅chatter 话题
- 旋转,等待消息到来
- 当消息到来时,chatterCallback()函数被召唤
3.建立你的节点
你在之前的教程中使用了catkin_create_pkg,这个为你创造了一个package.xml和一个CMakeLists.txt。
产生的CMakeList.txt应该看起来像这样(有从Creating Msgs and Srvs来的修改以及没有使用的注释和实例移除):
https://raw.github.com/ros/catkin_tutorials/master/create_package_modified/catkin_ws/src/beginner_tutorials/CMakeLists.txt
1 cmake_minimum_required(VERSION 2.8.3)
2 project(beginner_tutorials)
3
4 ## Find catkin and any catkin packages
5 find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
6
7 ## Declare ROS messages and services
8 add_message_files(DIRECTORY msg FILES Num.msg)
9 add_service_files(DIRECTORY srv FILES AddTwoInts.srv)
10
11 ## Generate added messages and services
12 generate_messages(DEPENDENCIES std_msgs)
13
14 ## Declare a catkin package
15 catkin_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应该看起来像这样:
https://raw.github.com/ros/catkin_tutorials/master/create_package_pubsub/catkin_ws/src/beginner_tutorials/CMakeLists.txt
1 cmake_minimum_required(VERSION 2.8.3)
2 project(beginner_tutorials)
3
4 ## Find catkin and any catkin packages
5 find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
6
7 ## Declare ROS messages and services
8 add_message_files(FILES Num.msg)
9 add_service_files(FILES AddTwoInts.srv)
10
11 ## Generate added messages and services
12 generate_messages(DEPENDENCIES std_msgs)
13
14 ## Declare a catkin package
15 catkin_package()
16
17 ## Build talker and listener
18 include_directories(include ${catkin_INCLUDE_DIRS})
19
20 add_executable(talker src/talker.cpp)
21 target_link_libraries(talker ${catkin_LIBRARIES})
22 add_dependencies(talker beginner_tutorials_generate_messages_cpp)
23
24 add_executable(listener src/listener.cpp)
25 target_link_libraries(listener ${catkin_LIBRARIES})
26 add_dependencies(listener beginner_tutorials_generate_messages_cpp)
这将会创造两个可执行文件, talker和listener,将会默认地进入你devel space的package目录,默认地址是 ~/catkin_ws/devel/lib/< package name>。
注意到你已经为可执行目标增加了依赖文件到信息产生目标:
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
这个确保了这个package的信息header在使用之前产生。如果你使用你的catkin workspace的其他package的消息,你将会需要增加依赖文件到各自的生成目标,因为catkin 平行构建所有的文件,对于Groovy,可以使用以下变量来依赖所有必要的目标:
target_link_libraries(talker ${catkin_LIBRARIES})
你可以直接调用可执行文件或者你可以使用rosrun来调用。它们没有被放置在 ‘< prefix >/bin’ 因为在安装你的package到系统的时候将会污染PATH。如果你希望你的可执行文件在安装的时候在PATH,你可以建立一个安装目标,看:catkin/CMakeLists.txt
对于更多的CMakeList.txt的描述,请看catkin/CMakeLists.txt
现在运行catkin_make:
# In your catkin workspace
$ cd ~/catkin_ws
$ catkin_make
注意:如果你在增加作为一个新的pkg,你可能会需要告诉catkin强制制作–force-make选项。看catkin/Tutorials/using_a_workspace#With_catkin_make
既然你已经写了一个简单的发布者和订阅者,让我们检查examine the simple publisher and subscriber。
4.额外的资源
这儿有一些额外的资源,由社区贡献:
4.1视频教程
以下是视频,展现一个小的教程,解释如何在ROS中写和测试一个发布者和订阅者,用C++和Python,基于上面提到的talker/listener(完整的视频在my Udemy Course on ROS(https://www.udemy.com/ros-essentials/?couponCode=ROSTUTORIALS))