机器人操作系统ROS Indigo 入门学习(12)——用C++语言写一个简单的发布者和订阅者

本文主要是介绍机器人操作系统ROS Indigo 入门学习(12)——用C++语言写一个简单的发布者和订阅者,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这个教程将会包含怎样用C++去写一个发布者和订阅者.

 

1.1写一个发布者Node

“Node”是连接在ROS网络中一个可执行单元的术语.这里我们创建一个会不断广播messages的发布者(“talker”)node.

改变目录到你之前创建的工作空间的beginner_tutorials package中:

cd ~/catkin_ws/src/beginner_tutorials

 

1.1.1代码

在beginner_tutorials packag目录中创建一个srv目录:

mkdir -p ~/catkin_ws/src/beginner_tutorials/src

这个目录会包含所有beginner_tutorials package中的源文件.

在beginner_tutorials package中创建一个src/talker.cpp文件.并且把下面的代码粘贴上去":

https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/talker/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;}


 

 

 

1.1.2代码解释

现在我们分解代码.

#include “ros/ros.h”

ros/ros.h是一个非常方便的头文件它包含了最常用的ROS系统部分所必须的一些头文件.

#include “std_msgs/String.h

这里包含了std_msgs package中的std_msgs/String message .这个头文件自动的从String.msg.file中产生.更多关于message的信息,请查看msg page.

 

 

Ros::init(argc,argv,”talker”);

初始化ROS.这个允许ROS通过命令行重新映射名字 –现在不重要.同样可以用来指定node的名字.在系统中Nodes的名字必须是唯一的.

名字必须是一个基本的名字(base name),比如,不能有/在里面.

 

Ros::NodesHandle n;

为这个node创建一个handle.创建的第一个NodeHandle用来初始化node,最后一个销毁的NodeHandle会清除所有node占有的资源.

 

 ros::Publisher chatter_pub = n.advertise<std_msgs::

String>("chatter", 1000);

告诉master我们将要在chatter topic中要发布一个std_msgs::String类型的message,这就会让master告诉所有的nodes听取chatter这个topic,在这个tpoic上在我们将要发布数据.第二个参数是发布队列的大小.这样的话如果我们发布的太快,在开始丢弃之前的message前,它会最大缓冲1000个messages.

 

NodeHandle::advertise()返回一个ros::Publisher的对象,它有两个作用:(1)它允许你发布message到它创建的topic上的publish()(2)当它超出范围时,会自动解除广播.

 

ros::Rate loop_rate(10);

ros::Rate对象会指定一个你想循环的频率.它会跟踪距离上次调用Rate::sleep(),有多长时间了,并且休眠正确长度的时间.

这里我们设置为10hz:

 

int count  =0;

while(ros::ok())

{}

默认roscpp会安装一个SIGINT信号处理函数提供对ctrl+c的处理,ctrl+c会导致ros::ok()返回错误.

 

ros::ok()会返回错误如果:

接受到SIGINT(ctrl+c)

我们通过用另一个有同样名字的node网络

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();

 

我们使用适应message的类在ROS上广播了一个message,通常从一个msg文件产生出来。其他复杂的数据类型也是是可以的,但是现在我们准备用标准的String message,它有一个成员:”data”。

chatter_pub.publish(msg);

现在实际上我们在向任何一个连接上的人广播这个message.

 

ROS_INFO("%s", msg.data.c_str());

 

ROS_INFO和它的友元类都是用来替代printf/cout的。更多信息请看rosconsole documentation

 

    ros::spinOnce();

 

对于这个程序调用ros::splinOnce()不是必要的,因为我们不会接受到任何回叫信号。然而,如果你打算为这个应用添加一个订阅,并且没有调用ros::splinOnce(),你绝不会得到回叫信号,所以还是添加的好。

loop_rate.sleep();

 

现在使用ros::Rate对象去空耗掉剩下的时间以满足10hz的发布速度。

 

这里是步骤的简要描叙:

初始化ROS系统

广告给master我们将要发布std_msgs/Stringmessage到chatter topic上

在发布message的时候循环以满足10次每秒钟

 

1.2写一个订阅者 Node

1.2.1代码

beginner_tutorials package中src目录下创建listener.cpp文件,并且把下面的代码粘贴进去:

https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/listener/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.2.2代码解释

现在,我们把代码打断成一段段的,忽略上面已经分析过的部分

 34 void chatterCallback(const std_msgs::String::ConstPtr& msg)

  35 {

  36   ROS_INFO("I heard: [%s]", msg->data.c_str());

  37 }

当一个新的message抵达chatter topic时这个回调函数会被调用.这个message以boost shared_ptr,的形式传递,这意味着你可以储存它,而不用担心它会在被删除,并且无需拷贝底层的数据.

 

 ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

在master启动的前提下订阅chatter topicROS会调用chatter Callback()函数只要一个新的message到达时.第二个参数是队列的大小,假设我们没有足够的能力去使发送message足够的快.这样的话,如果队列达到1000个messages,随着新的message的到来,我们会开始丢掉旧的message.

 

NodeHandle::subscribe()会返回一个ros::Subscriber 对象,你必须坚持这个订阅对象直到你想取消订阅.当订阅对象被摧毁时,它会自动取消订阅chatter topic.

 

这里有不同版本的NodeHandle::subscribe()函数允许你指定一个类的成员函数,或者甚至任何被BoostFunction对象调用的东西,roscpp overview包含更多的信息.

 

ros::spin();

 ros::spin()进入了一个循环,调用message回调尽可能的快.即使这样,但不用担心,如果没有什么要做就不会占用许多CPU资源,ros::spin()会退出一旦ros::ok()返回错误,这意味着ros::shutdown()被调用了,不是被默认的Ctrl+c处理函数然后master告诉我们去关机,就是被手动调用.

 

还有其他调用回调函数的方法,但是这里我们不关心它.roscpp_tutorials package有一些关于这个的应用演示roscpp overview 也包含更多的信息.

这里再一次简要的概括以上内容:

初始化ROS系统

订阅chatter topic

Spin,等待message到来

当一个message到来时,chatterCallback()函数被调用

 

 

1.3编译代码

在之前的教程中你用catkin_create_pkg去创建一个package.xml和一个CMakeLists.txt文件.

 

产生的这个CMakeLists.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文件就好了:

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)


 

 

 

最后的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 packages5 find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)6 7 ## Declare ROS messages and services8 add_message_files(FILES Num.msg)9 add_service_files(FILES AddTwoInts.srv)10 11 ## Generate added messages and services12 generate_messages(DEPENDENCIES std_msgs)13 14 ## Declare a catkin package15 catkin_package()16 17 ## Build talker and listener18 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的package目录中,默认是在~/catkin_ws/devel/lib/share/<package name>

 

注意你应该为可执行目标添加依赖到message generation 目标:

add_dependencies(talker beginner_tutorials_generate_messages_cpp)

 

这就可以保证package的message header在使用之前可以生成,如果你使用你工作空间的其他package产生的messages,你也需要为它们单独生成的目标添加依赖。

 

因为catkin平行编译所有的工程。如果是“Groovy”你可以用下面的变量去依靠所有必须的目标:

add_dependencies(talker ${catkin_EXPORTED_TARGETS})

 

如果你可以使用rosrun去调用他们,可以直接调用它们。他们不是放在'<prefix>/bin' 中因为在安装package到你的系统时会破坏PATH.如果你希望你的可执行文件安装的时候在PATH上,你可以建立一个安装目标,查看:catkin/CMakeLists.txt

 

 

catkin/CMakeLists.txt的更多详细的描叙请看:catkin/CMakeLists.txt

 

 

现在运行:

$ catkin_make

 

注意:如果你在添加一个新的pkg,也许需要告诉catkin去强制编译通过—force-cmake选项。参阅catkin/Tutorials/using_a_workspace#With_catkin_make.

这篇关于机器人操作系统ROS Indigo 入门学习(12)——用C++语言写一个简单的发布者和订阅者的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1001510

相关文章

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要