ROS 编写一个简单的发布者和订阅者(C++)
0 编写发布者节点
节点是在ROS里面的一个专业术语,它可以被ROS的网络所链接。在这里我们将创建一个名叫“talker”的发布者节点,它将连续的广播一个消息。
改变你现在的位置到你之前在catkin 工作区域里建立的beginner_tutorials 包。
0.1 代码
在beginner_tutorials包里建立一个src文件夹:
这个目录将包含我们所建立的beginner_tutorials的源文件。
在beginner_tutorials里面创建一个src/talker.cpp文件,然后将下面的代码贴到里面:
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> /** * This tutorial demonstrates simple sending of messages over the ROS system. */ int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. * For programmatic remappings you can use a different version of init() which takes * remappings directly, but for most command-line programs, passing argc and argv is * the easiest way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "talker"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The advertise() function is how you tell ROS that you want to * publish on a given topic name. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. After this advertise() call is made, the master * node will notify anyone who is trying to subscribe to this topic name, * and they will in turn negotiate a peer-to-peer connection with this * node. advertise() returns a Publisher object which allows you to * publish messages on that topic through a call to publish(). Once * all copies of the returned Publisher object are destroyed, the topic * will be automatically unadvertised. * * The second parameter to advertise() is the size of the message queue * used for publishing messages. If messages are published more quickly * than we can send them, the number here specifies how many messages to * buffer up before throwing some away. */ ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); /** * A count of how many messages we have sent. This is used to create * a unique string for each message. */ int count = 0; while (ros::ok()) { /** * This is a message object. You stuff it with data, and then publish it. */ std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); /** * The publish() function is how you send messages. The parameter * is the message object. The type of this object must agree with the type * given as a template parameter to the advertise<>() call, as was done * in the constructor above. */ chatter_pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++count; } return 0; }
0.2 代码的解释
现在我们来分割代码.
#include "ros/ros.h" ros/ros.h是一个十分方便的头文件,它包含了打多数ROS系统里使用公共共有部分的所有头文件。 <pre name="code" class="cpp">#include "std_msgs/String.h" 这个头文件存在于std_msgs包中,它是在这个包里由String.msg文件自动生成的。关于更多的对于消息的定义,可以参考msg页。 <pre name="code" class="cpp">ros::inti(argc ,argv, "talker");这是安装ROS,它允许ROS可以通过命令行对名称进行重映射,但是现在不是特别重要。
它也允许我们对我们的节点进行专有名称的指定。不过,节点的名称必须是唯一的。这个名字必须是一个基本名称,不能有/在里面。
ros::NodeHandle n;创建处理这一过程的节点。在第一个NodeHandle创建的时候将会初始化节点,在最后一个NodeHandle消失的时候将会清除所有节点
使用的资源。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000); 告诉master我们将要在chatter的话题中发布一个std_msgs/String类型的消息。它允许master告诉所有正在监听chatter的节点,我们将 要在这话题里面发布数据了。第二个参数是我们发布队列的大小。通过这种方式,我们发布的够快的话,它将可以在开始清楚旧的数 据之前,缓存最大1000个消息。 NodeHandle::advertise() 返回一个 ros::Publisher对象,它为两个目的服务:1)它包含了一个publish()方法,会让你在你创建的话题里 面发布消息;2)当它越界的话,它将不被通知。 <pre name="code" class="cpp">ros::Rate loop_rate(10); 一个 ros::Rate对象允许你制定一个回送的频率。它将会记录自从上一次Rate::sleep()到现在多长时间,并且在正确时间数的时候沉睡。 在这个例子里我们希望在10hz的运行。 <pre name="code" class="cpp">int count = 0; while (ros::ok()) {通过默认roscpp将会案子一个SIGINT handler,这个handler可以使用Ctrl-C来处理,如果触发了话,它将会导致ros::ok()返回false。
ros::ok()将会返回false如果:
- 一个SIGINT 被接受了(Ctrl-C)
- 我们因为另外一个相同名字的节点被网络踢出去了
- ros::shutdown()已经被应用的另外一个部分调用了
- 所有的ros::NodeHandles被销毁了
一旦ros::ok()返回false,所有的ROS 调用将会失败。
std_msgs::String msg;; std::stringstream ss; ss<<"hello world" <<count; msg.data =ss.str();
我们在ROS里面使用一个消息更新的类来广播消息,这个类通常在msg文件里形成。更加复杂的数据结构也是可能的,但我们这里使用了一个标准的String消息,它有一个成员:“data”。
chatter_pub.publish(msg);我们实际上对所有链接它的都广播了消息。
ROS_INFO("%s", msg.data.c_str()); ROS_INFO 和友类是我们对printf/count 的代替品。 ros::spinOnce(); 对于这个简单的程序,调用ros::spinOnce()不是必须的,因为我们没有收到任何的回调。然而,如果你在这个应用里添加一个订阅者 并且不使用ros::spinOnce(),你的回调将不会被调用。所以最好的办法是加上它。 loop_rate.sleep(); 现在我们使用ros::Rate对象来设置沉睡,然后在10hz订阅速度的时候提醒我们。 下面有一个精简版的原理:
- 初始化ROS系统;
- 建议我们将在chatter话题里对master发布一个std_msgs/String 的消息
- 当在发布消息时一秒十次将会回环(loop)
现在我们需要写一个接受消息的节点。
1 编写接受消息的节点
1.1 代码
在beginner_tutorials里面创建一个src/listener.cpp文件,复制下面的代码到里面去:
#include "ros/ros.h" #include "std_msgs/String.h" /** * This tutorial demonstrates simple receipt of messages over the ROS system. */ void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. * For programmatic remappings you can use a different version of init() which takes * remappings directly, but for most command-line programs, passing argc and argv is * the easiest way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "listener"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The subscribe() call is how you tell ROS that you want to receive messages * on a given topic. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. Messages are passed to a callback function, here * called chatterCallback. subscribe() returns a Subscriber object that you * must hold on to until you want to unsubscribe. When all copies of the Subscriber * object go out of scope, this callback will automatically be unsubscribed from * this topic. * * The second parameter to the subscribe() function is the size of the message * queue. If messages are arriving faster than they are being processed, this * is the number of messages that will be buffered up before beginning to throw * away the oldest ones. */ ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); /** * ros::spin() will enter a loop, pumping callbacks. With this version, all * callbacks will be called from within this thread (the main one). ros::spin() * will exit when Ctrl-C is pressed, or the node is shutdown by the master. */ ros::spin(); return 0; }
1.1代码解释
现在我们分开来进行,忽略一些之前解释过的。
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
这是一个回调函数,它将在一个新的消息到达chatter话题的时候进行回调。这个消息在一个boost shared_ptr中被传递,这意味这你如
果想,可以将它储存为off,不用担心他在你的下层被删除,不用负责潜在的数据。
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
用master来对chatter话题进行订阅。ROS每当一个新的消息到达时会调用一个chatterCallback()函数。第二个参数是队列的大小,以便我们不能快速的处理消息,通过这种方式,如果队列到达了1000个消息,我们将在下一个新的消息到来的时候扔掉旧的。
NodeHandle::subscribe()返回一个ros::Subscriber对象,这个对象你必须一直存在除非你不希望订阅了。当这个订阅对象被销毁时,它
将自动不被chatter话题订阅。
这里有一些版本的NodeHandle::subscribe()函数,它允许你知名一个类的成员函数,甚至是被一个Boost.Function object调用的任何东西。
这个roscpp overview 包含了更多的信息。
ros::spin();
ros::spin()进入了一个回环,这个回环尽可能快的调用了消息回调。不用担心,它不会使用过多的CPU。一旦ros::ok()返回false,ros::spin()
将会退出。这意味着ros::shutdown()已经被调用了,既不是通过默认的Ctrl-C handler让master告诉我们要关闭,也不是手动调用了它。
和上面一样,这里有一个精简的版本:
- 初始化ROS系统
- 订阅chatter话题
- 运行,等待消息到达
- 当一个消息到达后,chatterCallback()函数被调用
3. 建立你自己的节点
你可以使用之前Catkin_create_pkg里面创建的package.xml和CMakeList.txt文件。
形成的CMakeList.txt应该像下面的:
cmake_minimum_required(VERSION 2.8.3) project(beginner_tutorials) ## Find catkin and any catkin packages find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg) ## Declare ROS messages and services add_message_files(DIRECTORY msg FILES Num.msg) add_service_files(DIRECTORY srv FILES AddTwoInts.srv) ## Generate added messages and services generate_messages(DEPENDENCIES std_msgs) ## Declare a catkin package catkin_package()
不用担心修改了注释的例子,简单的添加这写行到你的CMakeList.txt的底部。
include_directories(include ${catkin_INCLUDE_DIRS}) 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)
它将会创建两个可执行文件——talker和listener,它们默认会到你的devel space里面的包的文件夹下,文件夹默认在 ~/catkin_ws/devel/lib<package_name>。
记住你为可执行文件目标必须添加依赖到消息生成目标:
add_dependencies(talker beginner_tutorials_generate_messages_cpp)这个确保这个包的消息头文件在被使用之前已经形成了。如果你从其他包里使用的消息在你catkin工作区里,你也需要为他们各自形成的目标添加依赖,因为catkin建立是平行的建立所有的工程的。对于*Groovy*你可以使用下面的来决定所有的目标:
target_link_libraries(talker ${catkin_LIBRARIES}) 你能直接调用或者你能使用rosrun来调用他们。它们不在"<prefix>/bin",因为当安装你的包到系统里去时将会污染PATH。 如果你希望你的可执行文件在安装的时候在PATH里面,你能设置一个安装目标,可以参考:catkin/CMakeList.txt 现在运行 catkin_make: 如果出现了 The specified base path "/home/exbot/catkin_ws/src/beginner_tutorials" contains a package but "catkin_make" must be invoked in the root of workspace 的错误,是因为catkin_make这个命令只能在工作区顶层运行,它只会编译~/catkin_ws/src下的源码。如果想要在编译 其他文件夹下的源码可以这样:
source后面的是你扔源码的路径。
或者
<pre name="code" class="cpp"><pre name="code" class="cpp">
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: C++里面的print()函数的问题
- 下一篇:没有了