第1章 绪论



机器人操作系统浅析书本结构




第2章 入门概述

1.功能包/软件包(Packages):
在ROS中,所有软件都被组织为软件包的形式,称为ROS软件包或功能包,有时也简称为包。ROS软件包是一组用于实现特定功能的相关文件的集合,包括可执行文件和其他支持文件。




2.节点管理器(The Master):ROS的一个基本目标是使节点(node)能够同时运行,ROS中实现通信的关键部分就是ROS节点管理器。


大多数ROS节点在启动时连接到节点管理器上,如果运行中连接中断,则不会尝试重新连接。因此,如果roscore被终止,当前运行的其他节点将无法建立新的连接,即使稍后重启roscore也无济于事。




3.节点(Nodes):

启动节点(也称运行ROS程序)的基本命令: rosrun

利用图形查看节点之间的连接关系:rqt_graph [qt_graph本身就是一个节点,]




4.话题和消息
:ROS节点之间进行通信所利用的最重要的机制就是消息传递。消息传递的理念是:当一个节点想要分享信息时,它就会发布(publish)消息到对应的一个或者多个话题;当一个节点想要接收信息时,它就会订阅(subscribe)它所需要的一个或者多个话题。




5.消息和消息类型:
获取当前活跃的话题:rostopic list 打印消息内容: rostopic echo topic-name 查看消息类型: rosmsg show
message-type-name 用命令行发布消息: rostopic pub –r rate-in-hz topic-name message-type
message-content



6.理解消息类型的命名

和ROS里其他的程序一样,每条消息类型都属于一个特定的包。消息类型名总会包含一个斜杠,斜杠前面的名字是包含它的包:
package-name/type-name



7.话题通信的多对多机制:不管哪个节点发布了该话题/turtle1/cmd_vel的消息,这些消息将会传送给每个订阅了该话题的节点。




节点之间存在松耦合关系,ROS为更加直接的一对一通信提供了一种称为服务(services)的机制。




第3章 编写ROS程序

1.创建工作区和功能包:创建一个新ROS功能包的命令应该在你工作区中的src目录下运行

catkin_create_pkg package-name 【创建了一个存放这个功能包的目录,并在那个目录下生成了两个配置文件】


第一个配置文件,package.xml清单文件(这些信息只有在你公开发布代码时才变得重要。本着保持文档与实际功能同步的精神,至少填写description和maintainer两部分可能是比较合理的);
第二个文件,CMakeLists.txt,是一个Cmake的脚本文件(。这个文件包含了一系列的编译指令,包括应该生成哪种可执行文件,需要哪些源文件,以及在哪里可以找到所需的头文件和链接库)




2.编写简单程序:

ros::init函数初始化ROS客户端库 ros::NodeHandle(节点句柄)对象是你的程序用于和ROS系统交互的主要机制
ROS_INFO_STREAM宏将生成一条消息,且这一消息被发送到不同的位置,包括控制台窗口




3.编译程序:




4.声明依赖库
--为了给出依赖库,编辑包目录下的CMakeLists.txt文件,所依赖的其他catkin包可以添加到这一行的COMPONENTS关键字后面,如下所示:
find_package(catkin REQUIRED COMPONENTS package-names)



5.清单文件中列出依赖库--通过使用build_depend (编译依赖)和run_depend(运行依赖)两个关键字实现:
<build_depend>package-name</build_depend> <run_depend>package-name</run_depend>

清单文件中声明的依赖库并没有在编译过程中用到;如果你在此处忽略它们,你可能不会看到任何错误消息,直到发布你的包给其他人,他们可能在没有安装所需包的情况下编译你发布的包而导致报错。







6.声明可执行文件--CMakeLists.txt中添加两行,来声明我们需要创建的可执行文件。其一般形式是:
add_executable(executable-name source-files)
target_link_libraries(executable-name ${catkin_LIBRARIES})
7.编译工作区--catkin_make 因为被设计成编译你的工作区中的所有包,这个命令必须从你的工作区目录运行。




    Sourcing setup.bash--
最后的步骤是执行名为setup.bash的脚本文件,它是catkin_make在你工作区的devel子目录下生成的。
source devel/setup.bash

这个自动生成的脚本文件设置了若干环境变量,从而使ROS能够找到你创建的功能包和新生成的可执行文件。它类似于全局setup.bash,但是是专门为你的工作区量身定做的。除非目录结构发生变化,否则你只需要在每个终端执行此命令一次,即使你修改了代码并且用catkin_make执行了重编译。




8.执行程序: rosrun




9.发布者程序:




    9.1创建发布者对象 发布消息的实际工作是由类名为ros::Publisher的一个对象来完成的:

    ros::Publisher
    pub = node_handle.advertise<message_type>(topic_name,
    queue_size);(注意话题名和消息类型的区别)




    9.2创建并填充消息对象




    9.3发布消息 使用ros::Publisher对象的publish方法可以很简单地发布消息:

    pub.publish(msg);




    9.4定义输出格式




   9.5 消息发布循环

    节点是否停止工作的检查 ros::ok()

    控制消息发布频率 ros::Rate
    rate(2);




   9.6 编译执行




10.订阅者程序:




    编写回调函数

    发布和订阅消息的一个重要的区别是订阅者节点无法知道消息什么时候到达。为了应对这一事实,我们必须把响应收到消息事件的代码放到回调函数里,ROS每接收到一个新的消息将调用一次这个函数。订阅者的回调函数类似于:
void function_name(const package_name::type_name &msg) { . . . }

其中参数package_name

和type_name和发布消息时的相同,它们指明了我们想订阅的话题的消息类。回调函数的主体有权限访问接收到消息的所有域,并以它认为合适的方式存储、使用或丢弃接收到的数据。




创建订阅者对象 :
ros::Subscriber sub = node_handle.subscribe (topic_name,queue_size,
pointer_to_callback_function);
最后一个参数是指向回调函数的指针,当有消息到达时要通过这个指针找到回调函数。在C++中,你可以通过对函数名使用符号运算符(&,“取址”)来获得函数的指针




给ROS控制权 只有当我们明确给ROS许可时,它才会执行我们的回调函数
ros::spinOnce();
这个代码要求ROS去执行所有挂起的回调函数,然后将控制权限返回给我们。另一个方法如下所示:
ros::spin();
这个方法要求ROS等待并且执行回调函数,直到这个节点关机。

换句话说,ros::spin()大体等于这样一个循环:
while(ros::ok( )) { ros::spinOnce(); }

使用ros::spinOnce()还是使用ros::spin()的建议如下:你的程序除了响应回调函数,如果没有其他重复性工作要做,那么使用ros::spin();否则,合理的选择是写一个循环做其他需要做的事情,并且周期性地调用ros::spinOnce()来处理回调。

忽略ros::spinOnce使程序表现的好像没有接收到任何消息。




编译运行




第4章 日志消息

ROS日志系统的核心思想,就是使程序生成一些简短的文本字符流,即日志消息。ROS中,日志消息分为五个不同的严重级别:
DEBUG
INFO
WARN
ERROR
FATAL
默认的日志级别是INFO,ROS的C++程序默认只生成INFO或者更高级别的消息,尝试生成DEBUG级别的消息将会被忽略。

第5章 计算图源命名

1.全局名称:

节点、话题、服务和参数统称为计算图源,而每个计算图源由一个叫计算图源名称(graph
resource name)的短字符串标识。

之所以叫做全局名称因为它们在任何地方(译者注:包括代码、命令行工具、图形界面工具等的任何地方)都可以使用。

2.全局名称的几个组成部分:

前斜杠“/”表明这个名称为全局名称;由斜杠分开的一系列命名空间(namespace),每个斜杠代表一级命名空间; 描述资源本身的基本名称(base name)




3.相对名称:

对计算图源名称(ralative graph
resource name),或简称为相对名称(relative name)。相对名称的典型特征是它缺少全局名称带有的前斜杠“/”。

4.解析相对名称
将相对名称转化为全局名称的过程,ROS将当前默认的命名空间的名称加在相对名称的前面,从而将相对名解析为全局名称。理解相对名称的关键是知道ROS解析某个计算图源时所使用的默认命名空间。

5.设置默认命名空间
默认的命名空间是单独地为每个节点设置的,而不是在系统范围进行。为节点选择一个不同的默认命名空间的最好也是最常用的方法是在启动文件中使用命名空间(ns)属性:
_ _ns:=default-namespace



6.私有名称:


私有名称,以一个波浪字符(~)开始,是第三类也是最后一类计算图源名称。和相对名称一样,私有名称并不能完全确定它们自身所在的命名空间,而是需要ROS客户端库将这个名称解析为一个全局名称。与相对名称的主要差别在于,私有名称不是用当前默认命名空间,而是用的它们节点名称作为命名空间。




第6章 启动文件 Launch file

利用启动文件一次性配置和运行多个节点。

ROS提供了一个同时启动节点管理器(master)和多个节点的途径,即使用启动文件(launch file)。
其基本思想是在一个XML格式的文件内将需要同时启动的一组节点罗列出来




执行启动文件 :
roslaunch package-name launch-file-name

roslaunch首先会判断roscore是否正在运行;如果没有,则自动启动roscore。不要将rosrun和roslaunch混为一谈,rosrun一次只能启动一个节点,而roslaunch可以同时启动多个节点。
文件内所有的节点几乎都在同一时刻启动,无法确定各个节点的启动顺序。




创建启动文件:将所有启动文件统一存放在一个子目录中,该子目录通常取名为launch;最简单的启动文件由一个包含若干节点元素(node
elements)的根元素(root element)组成。

插入根元素 启动文件是XML文件,每个XML文件都必须要包含一个根元素,由一对launch标签定义:
<launch> … <launch>
每个启动文件的其他元素都应该包含在这两个标签之内。

启动节点 任何启动文件的核心都是一系列的节点元素,每个节点元素指向一个需要启动的节点。
<node pkg=”package-name” type=”executable-name” name=”node-name” />
请求复位

在启动了启动文件中所有的请求节点之后,roslaunch会监视每一个节点,记录哪一个节点是活跃的。对于每个节点,我们可以设置respawn属性为真,这样当节点停止的时候,
roslaunch会重新启动该节点。
respawn=”true”
这个功能是有意义的,比如在某个节点因为软件崩溃或硬件故障以及其他原因导致过早退出系统的时候会起到作用。

必要节点(requiring node) 复位之外的另一种策略是将一个节点声明为必要节点。
required=”true”
当一个必要节点终止的时候,roslaunch会终止所有其他活跃节点并退出。

为节点维护独立的窗口

使用roslaunch的一个缺点是所有的节点共享一个终端,而使用rosrun的时候,每个节点都有一个独立的终端。实现该目的的简洁方法——对节点元素使用启动前缀(launch-prefix)属性:
Launch-prefix=“command-prefix”

事实上,roslaunch在启动节点时的内部工作原理是调用相应的命令行工具,即rosrun。启动前缀的主要思想是在其命令行前面添加给出的命令行前缀。在我们的例子中,遥操作节点使用了这个功能:
launch-prefix=”xetrm-e” (在三个基本属性之后添加前缀属性)



在命名空间内启动节点:通常方法是使用一个启动文件,并对其节点元素配置命名空间(ns)属性:
ns=”namespace”(在三个基本属性之后添加空间属性)



名称重映射(Remapping names):在ROS应用任何重映射之前,所有的名称都要先解析为全局名称,包括重映射中的原始名称和新名称。

启动文件的其他元素:

如果想在启动文件中包含其他启动文件的内容(包括所有的节点和参数),可以使用包含(include)元素:
<include file=”path-to-launch-file”>

此处file属性的值应该是我们想包含的文件的完整路径。由于直接输入路径信息很繁琐且容易出错,大多数包含元素都使用查找(find)命令搜索功能包的位置来替代直接输入路径:
<include file=”$(find package-name)/launch-file-name”>
启动参数(launch arguments):

参数赋值 :启动文件中的每一个参数都要进行赋值:
roslaunch package-name launch-file-name arg-name:=arg-value
除此之外,你也可以使用以下两种语法,将参数值作为arg声明的一部分:
<arg name=”arg-name” default=”arg-value”/> <arg name=”arg-name”
value=”arg-value”/>
两者的唯一区别在于命令行参数可以覆盖默认值default,但是不能覆盖参数值value。

创建组(Creating groups):组(group)元素,它提供了一种在大型启动文件内管理节点的便捷方式

组可以把若干个节点放入同一个命名空间内:
<group ns=”namespace”/> … </group>
组内的每个节点都从给定的默认命名空间启动。

组可以有条件地使能或禁用一个节点:
<group if=”0 or 1”/> … </group>



第7章 参数

ROS还提供另一种参数(parameters)机制用于获取节点的信息。其主要思想是使用集中参数服务器(parameter
server)维护一个变量集的值,包括整数、浮点数、字符串以及其他数据类型,每一个变量用一个较短的字符串标识。

所有的参数都属于参数服务器而不是任何特定的节点。这意味着参数——即使是由节点创建的——在节点终止时仍将继续存在。
查询参数 :rosparam get parameter_name 设置参数 :rosparam set parameter_name
parameter_value
创建和加载参数文件 为了以YAML文件的形式存储命名空间中的所有参数,可以使用rosparam dump命令:
rosparam dump filename namespace
从一个文件中读取参数,并将它们添加到参数服务器:
rosparam load filename namespace



第8章 服务


服务调用是双向的,一个节点给另一个节点发送信息并等待响应,因此信息流是双向的。服务调用实现的是一对一通信。每一个服务由一个节点发起,对这个服务的响应返回同一个节点。




从命令行查看和调用服务
列出所有服务 :rosservice list 查找提供服务的节点 即查找提供给定服务的节点:rosservice node service-name
查看服务数据类型 :rossrv show service-data-type-name 从命令行调用服务 :rosservice call
service-name request-content



客户端程序






* 声明请求和响应的类型
* 创建客户端对象
* 创建请求和响应对象
* 调用服务
* 声明依赖



服务器程序


创建一个ros::ServiceServer来代替ros::Subscriber,唯一的区别在于服务器端可以通过一个响应对象和一个表明成功与否的布尔变量给客户端回传数据。



* 编写服务的回调函数,服务的回调函数原型如下:
* 创建服务器对象
* 提供ROS控制权bool function_name( package_name::service_type::Request &req),
package_name::service_type::Response &resp) ) { … }




第9章 消息录制与回放


通过rosbag,我们能够将发布在一个或者多个话题上的消息录制到一个包文件中,然后可以回放这些消息,重现相似的运行过程。将这两种能力结合,便形成了测试某些机器人软件的有效方式:我们可以偶尔运行机器人,运行过程中录制关注的话题,然后多次回放与这些话题相关的消息,同时使用处理这些数据的软件进行实验。




录制与回放包文件
录制包文件 :rosbag record -O filename.bag topic-names 检查文件包 :rosbag info
filename.bag 回放包文件:rosbag play filename.bag