Client Library与roscpp
- roscpp ROS的C++库,是目前最广泛应用的ROS客户端库,执行效率高
- rospy ROS的Python库,开发效率高,通常用在对运行时间没有太大要求的场合,例如配置、初始化等操作
从开发客户端库的角度看,一个客户端库,至少需要能够包括master注册、名称管理、消息收发等功能。这样才能给开发者提供对ROS通信架构进行配置的方法。 整个ROS包括的packages如下,你可以看到roscpp、rospy处于什么位置。
roscpp
roscpp位于/opt/ros/kinetic之下,用C++实现了ROS通信。在ROS中,C++的代码是通过catkin这个编译系统(扩展的CMake)来进行编译构建的。把roscpp就当作为一个C++的库,我们创建一个CMake工程,在其中include了roscpp等ROS的libraries,这样就可以在工程中使用ROS提供的函数了。
调用ROS的C++接口,首先就需要#include <ros/ros.h>
。
roscpp的主要部分包括:
- ros::init() : 解析传入的ROS参数,创建node第一步需要用到的函数
- ros::NodeHandle : 和topic、service、param等交互的公共接口
- ros::master : 包含从master查询信息的函数
- ros::this_node:包含查询这个进程(node)的函数
- ros::service:包含查询服务的函数
- ros::param:包含查询参数服务器的函数,而不需要用到NodeHandle
- ros::names:包含处理ROS图资源名称的函数
以上功能可以分为以下几类:
- Initialization and Shutdown 初始与关闭
- Topics 话题
- Services 服务
- Parameter Server 参数服务器
- Timers 定时器
- NodeHandles 节点句柄
- Callbacks and Spinning 回调和自旋(或者翻译叫轮询?)
- Logging 日志
- Names and Node Information 名称管理
- Time 时钟
- Exception 异常
节点初始 关闭及NodeHandle
当执行一个ROS程序,就被加载到了内存中,就成为了一个进程,在ROS里叫做节点。每一个ROS的节点尽管功能不同,但都有必不可少的一些步骤,比如初始化、销毁,需要通行的场景通常都还需要节点的句柄。
初始化节点
对于一个C++写的ROS程序,之所以它区别于普通C++程序,是因为代码中做了两层工作:
- 调用了ros::init()函数,从而初始化节点的名称和其他信息,一般我们ROS程序一开始都会以这种方式开始。
- 创建ros::NodeHandle对象,也就是节点的句柄,它可以用来创建Publisher、Subscriber以及做其他事情。 句柄(Handle)这个概念可以理解为一个“把手”,你握住了门把手,就可以很容易把整扇门拉开,而不必关心门是什么样子。NodeHandle就是对节点资源的描述,有了它你就可以操作这个节点了,比如为程序提供服务、监听某个topic上的消息、访问和修改param等等。
关闭节点
要关闭一个节点可以直接在终端上按Ctrl+C,系统会自动触发SIGINT句柄来关闭这个进程。 也可以通过ros::shutdown()来手动关闭节点.
节点初始化、关闭的例子。
|
|
一个ROS程序的执行步骤,通常要启动节点,获取句柄,而关闭的工作系统自动完成.
NodeHandle常用成员函数
NodeHandle是Node的句柄,用来对当前节点进行各种操作。在ROS中,NodeHandle是一个定义好的类,通过include<ros/ros.h>,我们可以创建这个类,以及使用它的成员函数。
NodeHandle常用成员函数包括:
|
|
NodeHandle对象在ROS C++程序里非常重要,各种类型的通信都需要用NodeHandle来创建完成。
回调函数与spin()方法
回调函数在编程中是一种重要的方法,在维基百科上的解释是: In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time.
回调函数作为参数被传入到了另一个函数中(在本例中传递的是函数指针),在未来某个时刻(当有新的message到达),就会立即执行。Subscriber接收到消息,实际上是先把消息放到一个队列中去,如图所示。队列的长度在Subscriber构建的时候设置好了。当有spin函数执行,就会去处理消息队列中队首的消息。
spin具体处理的方法又可分为阻塞/非阻塞,单线程/多线程,在ROS函数接口层面我们有4种spin的方式:
| spin方法 | 阻塞 | 线程 | | ---- | ---- | |ros::spin() |阻塞 |单线程| |ros::spinOnce() |非阻塞 |单线程| |ros::MultiThreadedSpin() | 阻塞 |多线程| |ros::AsyncMultiThreadedSpin() | 非阻塞 |多线程|
常用的spin()、spinOnce()是单个线程逐个处理回调队列里的数据。有些场合需要用到多线程分别处理,则可以用到MultiThreadedSpin()、AsyncMultiThreadedSpin()。
roscpp里有两种时间的表示方法,一种是时刻(ros::Time),一种是时长(ros::Duration)
int32 sec
int32 nsec
Time/Duration都由秒和纳秒组成。 要使用Time和Duration,需要#include <ros/time.h>
和#include <ros/duration.h>
|
|
Time和Duration表示的概念并不相同,Time指的是某个时刻,而Duration指的是某个时段,尽管他们的数据结构都相同,但是用在不同的场景下。 ROS重载了Time、Duration类型之间的加减运算,比如:
|
|
没有Time+Time的做法。
等待场景sleep
|
|
Rate的功能是指定一个频率,让某些动作按照这个频率来循环执行。与之类似的是ROS中的定时器Timer,它是通过设定回调函数和触发时间来实现某些动作的反复执行,创建方法和topic中的subscriber很像。
|
|
Exception
roscpp中有两种异常类型,当有以下两种错误时,就会抛出异常:
ros::InvalidNodeNameException 当无效的基础名称传给ros::init(),通常是名称中有/,就会触发
ros::InvalidNameExcaption 当无效名称传给了roscpp