ROS学习笔记
ROS功能包/软件包(Packages)
- ROS软件包是一组用于实现特定功能的相关文件的集合,包括可执行文件和其他支持文件。
- 所有的 ROS 软件都是一个软件包或其他软件包的一部分。
- 每个程序包由一个清单文件(文件名为 package.xml)定义。
- 该文件定义关于包的一些细节,包括其名称、版本、维护者和依赖关系。
- 包含 package.xml 文件的目录被称为软件包目录。
- 使用catkin编译构建系统的功能包, 编译产生的可执行文件存放在一个单独的标准化目录层次结构中。
- 功能包集(stack): 功能包集是紧密相关的功能包的集合,从groovy开始慢慢地被淘汰, 取而代之的是元功能包(metapackages)。
节点管理器(The Master)
- 接单(node)是几乎相对独立的小程序,这些节点必须能够通信, 通信的关键部分是ROS节点管理器。
- 启动节点管理器的命令 -- roscore。
- 大多数 ROS 节点在启动时连接到节点管理器上,如果运行中连接中断,则不会尝试重新连接。
- 因此,如果 roscore被终止,当前运行的其他节点将无法建立新的连接,即使稍后重启 roscore 也无济于事。
- roslaunch 的工具,其目的是一次性启动多个节点。
节点(Nodes)
- 一旦启动roscore后,便可以运行ROS程序了, ROS程序的运行实例被称为节点(node)。
- “运行实例”(running instance)很重要。
- 如果我们同时执行相同程序的多个副本——注意确保每个副本使用不同的节点名——则每个副本都被当做一个单独的节点。
- 启动节点:
- 利用命令rosrun: rosrun package-name executable-name;
- rosrun 没有什么“神奇”的:它只不过是一个 shell 脚本,能够理解 ROS 的文件组织结构,知道到哪里能找到与给定包名称对应的可执行文件。
- 通过节点管理器注册成为 ROS 节点发生在程序的内部,而不是通过 rosrun 命令。
- 查看节点列表:
- 利用命令: rosnode list;
- rosout 节点是一个特殊的节点,通过 roscore 自动启动,其作用类似于标准输出(即std::cout)。
- 节点名不一定与对应可执行文件名称相同。
- 获得特定节点的信息: rosmode info node-name;
- 终止节点: rosnode kill node-name;
- 终止和重启节点通常不会对其他节点有较大影响。
- 即使节点间正在相互交换消息(message),这些连接也会在节点终止时断开,在节点重启时重新连接。
- 将节点从列表中删除: rosnode cleanup。
话题和消息
- ROS节点之间进行通信所利用的最重要的机制就是消息传递。
- 在ROS中,消息有组织地存放在话题里。
- 消息传递的理念:
- 当一个节点想要分享信息时,它就会发布(publish)消息到对应的一个或者多个话题;
- 当一个节点想要接收信息时, 它就会订阅(subscribe)它所需要的一个或多个话题;
- ROS节点管理器负责确保发布节点和订阅节点能找到对方;
- 消息是直接地从发布节点传递到订阅节点,中间并不经过节点管理器转交;
- 查看节点构成的计算图:
- 查看节点之间的发布-订阅关系的最简单方式就是在终端输入rqt_graph命令。
- r代表ROS, qt指的是Qt图形界面(GUI)工具包;
- 在默认情况下,rqt_graph 隐藏了其认为只在调试过程中使用的节点。
- 的名称/rosout 既指节点又指话题。但 ROS 并不会因这种重复的名字而混淆,因为 ROS 会根据上下文来推测我们讨论的是/rosout 节点还是/rosout 话题。
- ROS 节点通常设计成了只管发布它们有用的信息,而不需要担心是否有其他节点来订阅这些消息。这样有助于减少各个节点之间的耦合度。
- ,/teleop_turtle 节点会以消息的形式将这些运动控制命令发布到 话 题 /turtle1/cmd_vel; 与此同时,因为turtlesim_node 订阅了该话题,因此它会接收到这个些消息,控制海龟按照该预定的速度移动。
话题列表
- 通过rostopic list获取当前活跃的话题;
- 打印消息内容rostopic echo topic-name;
- 测试发布频率rostopic hz topic-name(每秒发布消息的数量) 和 rostopic bw topic-name(每秒发布消息所占的带宽);
- Type在文本输出中表示数据类型;
- 查看消息类型: rosmsg show message-type-name。
- 一个复合域是由简单的一个或者多个子域组合而成,其中的每一个子域可能是另一个复合域或者独立的域,而且它们一般也都由基本数据类型组成。
用命令行发布消息
- 利用rostopic命令工具: rostopic pub -r rate-in-hz topic-name message-type message-content(按照指定的频率给指定的话题发布指定的消息);
- 该命令最后的参数 message-content 应该按顺序提供消息类型中所有域的参数值。
- rostopic pub -r 1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 3]'命令中利用了-r来指定话题以频率模式发布消息;
- 锁存模式只是发布一条消息,但锁存模式是默认的模式。
- -f是从文件中读取,输入应该 符合rostopic echo的输出格式。
- 编写一种脚本将 rostopic echo 和 rostopic pub 结合起来作为“记录”和“回放”消息的方式;rosbag工具。
理解消息类型的命名
- 每条消息类型都属于一个特定的包,消息类型名总会包含一个斜杆,斜杆钱面的名字是包含它的包: package-name/type-name;
- turtlesim/Color中的turtlesim是功能包名, Color是类型名称;
- ROS 节点管理器不允许多个节点拥有相同的名称。
话题通信的多对多机制
- 基于话题和消息的通信机制是多对多的,即多个发布者和多个订阅者可以共享同一个话题。
- 节点之间的松耦合关系:
- 每个节点都不需要显式知道其他节点的存在与否;它们的唯一交互方式是间接地发生在基于话题和消息的通信层。
- 节点之间的独立性,以及其支持的任务可分解特性(即复杂任务分解成可重用的小模块),是 ROS 最关键的设计特性之一。
- “生产”消息的程序(例如 turtle_teleop_key)只管发布该消息,而不用关心该消息是如何被“消费”的。
- “消费”消息的程序(例如 turtlesim_node)只管订阅该话题或者它所需要消息的所有话题,而不用关心这些消息数据是如何“生产”的。
- ROS为更加直接的一对一通信提供了一种称为服务(services)的机制。
- ROS 利用roswtf进行问题的查找。
- roswtf 将会检测在安装过程中 rosdep 初始化是否完成,任何节点是否出现了意外的挂起或者终止,以及活跃的节点是否正确地和其他节点相连接等。
- roswtf 检测的完整列表只能在 Python 源码中才能找到。
编写ROS程序
创建工作区和功能包
- 创建工作区,我们创建的包,应该全部放到一个叫做工作区的目录中。
- ROS的catkin编译系统试图一次性编译同一个工作区中的所有功能包,如果设计到几个相互独立的项目,则应该维护数个独立的工作区。
- 每个工作区必须创建一个src的子目录,这个子目录讲用于存放功能包的源码。
- catkin_create_pkg package-name是创建一个功能包。
- 一个配置文件 -- package.xml这是一个清单文件。
- CMakeLists.txt是一个Cmake的脚本文件, 其实catkin在内部使用了Cmake。
- ROS 包的命名遵循一个命名规范,只允许使用小写字母、数字和下划线,而且首字符必须是一个小写字母。一些 ROS工具,包括 catkin,不支持那些不遵循此命名规范的包。
- 本着保持文档与实际功能同步的精神,至少在package.xml中填写description 和 maintainer 两部分可能是比较合理的
- 包含消息的声明: 每一个话题都与一个消息类型相关联,每一个消息类型都有一个相对应C++头文件。
#include <package_name/type_name.h>头文件, 像#include <geometry_msgs/Twist.h>表示名为 geometry_msgs 的包所拥有的类型为 Twist 的消息。
编写回调函数
- 发布和订阅消息的一个重要的区别是订阅者节点无法知道消息什么时候才能到达, 为了对应这一事实, 我们必须把响应收到消息事件的代码放到回调函数里, ROS每接收到一个新的消息将调用一次这个函数。
日志消息
- 学习如何生成和查看日志消息。
- ROS中分为5个不同的严重级别,按严重性程度递增为: DEBUG、INFO、WARN、ERROR、FATAL。
- DEBUG: reading header from buffer.
- INFO: Waiting for all connections to establish.
- WARN: Less than 5GB of space free on disk.
- ERROR: Publisher header did not have required element: type.
- FATAL: You must call ros::init() before creating the first NodeHandle.
- 为日志系统本身就是面向行的, 没有必要使用std::endl或者其他的行终止符。
- 检查和清除日志文件:
- 这些日志文件将随着时间积累;
- rosrun和roslaunch运行时会检查和检测已经存在的日志的大小,并会在日志文件大小超过1GB时提醒,但是不会采取任何措施来减小日志文件的大小。
- 查看当前用户使用的日志文件的当前大小: rosclean check。
- 删除所有已经存在的日志: rosclean purge。
- 尝试生成DEBUG级别的消息将会被忽略。
- 设置日志级别类似于 rqt_consolt 中的日志级别过滤选项。
- ROS_INFO_STREAM 等构建方式是宏而不是函数调用。这些宏对应的展开代码会检查消息是否被启用,并且只会评估那些被启用消息的表达式。
- 通过命令行设置日志级别:
- rosservice call /node-name/set_logger_level ros.package-name level;
- 这条命令调用 set_logger_level 服务,该服务由各个节点自动提供。
- roservice 的参数 ros.package-name 是必需的,用来指明我们期望配置的日志记录器(logger)的名称。
- rqt_logger_level通过图形界面设置日志级别;
- 通过C++代码设置日志级别,最直接的方式是调用ROS来实现日志功能的log4cxx提供的代码接口
#include <log4cxx/logger.h>
. . .
log4cxx::Logger::getLogger(ROSCONSOLE_DEFAULT_NAME)->setLevel(
ros::console::g_level_lookup[ros::console::levels::Debug]
);
ros::console::notifyLoggerLevelsChanged();
- 调用 ros::console::notifyLoggerLevelsChanged()是有必要的,因为每个日志的启用或者禁用是缓存了的。
计算图源命名
- ROS的计算图资源(节点、话题、参数、和服务等)的命名和解析。
全局名称
- 节点、话题、服务和参数统称为计算图源,而每个计算图源由一个叫计算图源名称(graph resource name)的短字符串标识。
- 前斜杆"/"表明这个名称为全局名称, 由斜杆分开的一系列命名空间(namespace), 每个斜杆代表一级命名空间。
- 描述资源本身的基本名称(base name)。
相对名称
- 一个主要替代方案是让 ROS为计算图源提供一个默认的命名空间,具有此特征的名称叫做相对计算图源名称(ralative graph resource name),或简称为相对名称(relative name)。
- 设置默认命名空间:
- 默认的命名空间是单独地为每个节点设置的,而不是在系统范围进行。
- 大部分 ROS 程序,包括调用 ros::init 的所有 C++程序,接受叫做_ns 的命令行参数,此参数将为程序指定一个默认命名空间。 _ns:=default-namespace;
- 还可以利用环境变量为在 shell 内执行的 ROS 程序设置默认命名空间。Export ROS_NAMESPACE=default-namespace
私有名称
- 私有名称,以一个波浪字符(~)开始,是第三类也是最后一类计算图源名称。
- 私有名称并不能完全确定它们自身所在的命名空间,而是需要 ROS 客户端库将这个名称解析为一个全局名称。
- 与相对名称的主要差别在于,私有名称不是用当前默认命名空间,而是用的它们节点名称作为命名空间。
匿名名称(Anonymous names)
- 匿名名称的目的是使节点的命名更容易遵守唯一性的规则。其思路是,当节点调用 ros::init 方法时可以请求一个自动分配的唯一名称。
- 为了请求一个匿名名称,节点需要将ros::init_options::Anonymous-Name 作为第四个参数传递给ros::init 方法:ros::init(argc, argv, base_name, ros::init_options::AnonymousName);
启动文件
- 利用启动文件一次性配置和运行多个节点,ROS提供了一个同时启动节点管理器(master)和多个节点的途径,即启动文件(launch file)。
- 启动文件需要roslaunch工具来进行启动。
使用启动文件
- roslaunch的基本思想是在一个XML格式的文件内将需要同时启动的一组节点罗列出来。
在命名空间内启动节点
- 对一个节点设置默认命名空间,这个过程通常叫做压人(pushing down)命名空间--的通常方法是使用一个启动文件,并对其节点元素配置命名空间(ns,ns = namesapce)属性。
- 事实上 roslaunch 要求启动文件中的节点名称是基名称,即不涉及任何命名空间的相对名称。如果节点元素的名称属性中出现了全局名称,roslaunch 将会报错。
名称重映射(Remapping names)
- 除了相对名称和私有名称, ROS节点还支持重映射(remapping), 它可以从更精细的层面控制对所用节点名称的修改。
- 重映射是基于替换的思想: 每个重映射包含一个原始名称和一个新名称。
- 每当节点使用重映射中的原始名称时, ROS客户端库就会将它默默地替换成其对应的名称。
启动参数(launch arguments)
- 为了使启动文件便于配置, roslaunch还支持启动参数,其功能有点像可执行程序中的局部变量;
- 这样的有点是通过设置参数来描述节点在不同ROS会话中运行时可能需要改变.
- 向包括的启动文件中发送参数值,参数仅定义在对其进行声明的启动文件中,而不能被包含的启动文件继承;
- 将arg元素作为一个包含元素的子元素。
<incluce file=”path–to-launch-file”>
<arg name=”arg-name” value=”arg-value”/>
…
</include>
创建组(Creating groups)
- 组元素(group)是启动文件的最后一个特征,它提供了一种在大型启动文件内管理节点的便捷方式。
- 组可以把若干个节点放入同一个命名空间内:
<group ns=”namespace”/>
…
</group>
- 组内的每个节点都从给定的默认命名空间启动。
- 如果一个组元素内的某个节点有它自己的命名空间属性,并且其名称是(原则上也应该是)相对名称,那么该节点将会在一个默认命名空间内启动,这个默认的命名空间是将此节点命名空间嵌入到组元素命名空间的结果。这个规则和前面讲到的名称解析是相符的,并且这个规则也适用于组元素的嵌套。
- 组可以有条件地使能或禁止一个节点:
<group if=”0 or 1”/>
</group>
- 只有 0 和 1 才是 if 和 unless 属性的合法取值。特别需要提醒的是,读者熟悉的布尔型运算符 AND 和 OR 在这里不能直接使用。
- 使用组可以减少代码重复——命名空间和条件设置仅出现一次——并且使启动文件的结构更加清晰。
参数
- ROS还提供另一种参 数(parameters)机制用于获取节点的信息。
- 使用集中参数服务器(parameter server)维护一个变量集的值, 包括整数,浮点数,字符串以及其他数据类型,每个变量用一个较短的字符串标识。
通过命令行获取参数
- 查看参数列表: rosparam list。
- 参数服务器是节点管理器的一部分,因此,它总是通过 roscore 或者 roslaunch 自动启动。
- 需要铭记的是,所有的参数都属于参数服务器而不是任何特定的节点。
- 查询参数:
- rosparam get parameter_name, 查询某个参数的值。
- rosparam get namespace, 通过查询全局命名空间,我们可以一次性看到所有参数的值。
- 设置参数:
- rosparam set parameter_name parameter_value, 修改一个已有参数或者创建一个新的参。
- 以YAML字典的形式表示参数和对应值的映射关系。
- 冒号后的空格是非常重要的,以确保 rosparam 将其作为一个/duck_colors 命名空间内的参数集,而不是全局命名空间中的单个字符串参数 duck_colors。
- 创建和加载参数文件:
- rosparam dump filename namespace, 了以 YAML 文件的形式存储命名空间中的所有参数。
- rosparam load filename namespace, 从一个文件中读取参数,并将它们添加到参数服务器。
- 一个好的策略是演示真正的ROS节点如何工作得更好案例,是turtle首先测试这些参数是否存在,当且仅当这些参数不存在时, 才至指定默认的蓝色。
- rosparam get 命令是获取背景参数的值。
- 如果节点关心它的一些或者所有参数是否改变,必须明确向参数服务器请求这些参数的值。
使用C++获取参数
- ROS参数的C++接口是相当简单的: void ros::param::set(parameter_name, input_value);和bool ros::param::get(parameter_name, output_value);
在启动文件中设置参数
- 设置参数:
- 可以使用param元素请求roslaunch设置参数值, 。
- 设置私有参数:
- 作为节点元素的子集时,param元素中给出的参数名总是被当做私有名称解析,无论它们是否以~或者/开始。
<node . . . >
<param name="param-name" value="param-value" />
. . .
</node>
// 启动文件中设置
<launch>
<node
pkg="turtlesim "
type="turtlesim_node"
name="turtlesim "
/>
<node
pkg="agitr "
type="pubvel_with_max"
name="publish_velocity "
>
<param name="max_vel" value="3" />
</node>
<node
pkg="agitr "
type="set_bg_color"
name="set_bg_color"
/>
</launch>
- 在文件中读取参数: 启动文件也支持与rosparam load等价的命令, 可以一次性从文件中加载多个参数:
<rosparam command="load" file="path-to-param-file" />
。
- 这里的参数文件通常是通过rosparam dump命令创建的。
<rosparam
command="load"
file="$(find package-name)/param-file"
/>
- 参数的思想虽然简单, 但是可以大大提高ROS节点的灵活性和可配置性。
服务
- 消息传递是ROS中节点通信的主要方法, 但确实受到了一定的限制,服务调用(service calls)是另一种通信的方法。
- 服务调用和消息的区别在两个方面:
- 服务调用是双向的, 一个节点给另一个节点发送信息并等待响应,因此信息流是双向的。消息发布后并没有响应的概念,甚至不能保证系统内有节点订阅了这些消息。
- 服务调用实现的是一对一通信, 每一个服务有一个节点发起,这对这个服务的响应返回同一个节点。另一方面,每一个消息都和一个话题相关,这个话题可能有很多的发布者和订阅者。
- 消息和服务十分相似。
服务的专用术语
- 一个客户端(client)节点发送一些称为请求(request)的数据到一个服务器(server)节点,并等待回应。
- 服务器节点接收到请求后, 采取一些行动(计算、配置软件和硬件、改变自身行为等),然后发送一些称为响应(response)的数据给客户端节点。
- 请求和响应数据携带的特定内容由服务数据类型(service data type)来决定,它与决定消息内容的消息类型是类似的,服务数据类型也是由一系列域构成;
- 唯一区别是,服务数据类型分为两部分,分别表示请求(客户端节点提供服务器节点)和响应(服务其节点反馈给客户端节点)。
从命令行查看和调用服务
- 服务通常由节点内部的代码调用;
- 列出所有服务: ros service list; 服务名是计算图源名称,同其他资源名称一样,可以划分为全局的、相对的或者私有的名称。rosservice list命令的输出是所有服务的全局名称。
- 服务通常将节点名用作命名空间来防止命名冲突,并且允许节点通过私有名称来提供服务。
- 查看某个节点的服务类型,查看一个特定节点提供的服务,使用rosnode info命令。
- 查找提供服务的节点: rosservice node service-name;
- 查看服务数据类型: 当服务的数据类型已知时, 我们可以使用rossrv指令来获得此服务数据类型的详情 -- rossrv show service-data-type-name。
- 服务数据类型中的请求或响应字段可以为空,甚至两个字段可以同时为空。
- 从命令行调用服务: rosservice call service-name request-content
客户端程序
- 声明请求和响应的类型,必须包含相关的头文件:
#include <package_name/type_name.h>
。 - 创建一个客户端对象,
ros::ServiceClient client = node_handle.serviceClient<service_type>(service_name);
。
- service_name是一个字符串,应当是一个相对名称。
- 与创建类似的 ros::Publisher 对象相比,创建 ros::ServiceClient 对象不需要队列大小。
- 调用服务: 一旦拥有了一个ServiceClient, 一个完整的Request以及Response, 我们就可以调用服务了: bool success = service_client.call(request, reponse);
- 这个方法实际上完成了定位服务器节点、传输请求数据、等待响应和存储响应数据等一系列工作。
- 声明依赖: 需要编辑CMakeLists.txt和清单文件packag.xml。
- 必须保证CMakeLists.txt中的find_package行涉及了turtlesim功能包: find_package(catkin REQUIRED COMPONENTS roscpp turtlesim)。
- 在package.xml中, 我们应当确保build_depend和run_depend元素中存在相同名称的包,turtlesim, turtlesim 或 turtlesim。
服务器程序
- 必须创建一个ros::ServiceServer来代替ros::Subscriber, 唯一的区别在于服务端可以通过一个响应对象和一个表明成功与否的不二比昂两给客户端回传数据。
- 节点每次接收到一个服务请求,ROS就执行一次回调函数,参数Request中包含了来自客户端的数据,回调函数的工作是给Response对象的数据成员赋值。
- 创建服务器对象: ros::ServiceServer server = node_handle.advertiseService(service_name,pointer_to_callback_function);
- 可以使用两个分开的线程:一个发布消息,一个处理服务回调。 尽管 ROS没有明确指出程序要使用多个线程,但如果可以的话,这是非常便于合作的。
- 可以用ros::spin来代替sleep/ros::spinOnce循环,并且利用计数器回调函数(timer callback)来发布消息。
消息录制与回放
- ROS 系统的一个重要特征便是系统中信息的消费者不应该关心信息的生产者。
- 这种体系架构最明显的体现是ROS主要使用的消息发布-订阅模型。
- 不论什么时刻,只要有消息被发布,其订阅节点就应该正常工作,而不管是哪个或是那些节点正在发布这些消息。
- rosbag工具,能够发布在一个或这多个话题上的消息录制到一个包文件中,然后可以回放这些消息,重现像是的运行过程。
录制与回放包文件
- 术语包文件(bag files)是指用于存储带时间戳的ROS消息的特殊格式文件,rosbag命令行工具可以用来录制和回放包文件。
- 录制包文件: rosbag record -O filename.bag topic-names。
- 如果不指定文件名,rosbag 将基于当前的日期和时间自动生成一个。
- 用rosbag record -a 记录当前发布的所有话题的消息。
- 用 rosbag record -j 启用包文件的压缩。
- 回放包文件: rosbag info filename.bag。
- 持续时间、消息计数以及话题列表三个字段似乎很有趣。
- 录制正方形轨迹的包文件,
rosbag record -O square.bag /turtle1/cmd_vel /turtle1/pose
。
启动文件里面的包文件
- 通过这两个可执行文件可以很容易地将包文件作为启动文件的一部分,方法是包含适当的节点元素。
// 录制节点
<node
pkg="rosbag"
name="record"
type="record"
args="-O filename.bag topic-names"
/>
// 回放节点
<node
pkg="rosbag"
name="play"
type="play"
args="filename.bag"
/>
- 在网络环境中运行ROS, ROS的一大优势是支持分布式机器人控制模式,即诸多程序运行在不同的计算机上,通过互相交互来完成指定任务。
- 需要在网络层和ROS层进行配置,网络层确保计算机之间能够互相通信, 而ROS层的配置是确保所有节点都能与节点管理器通信。
- 编写更规范的程序, 程序一定要注重可扩展性和可维护性;
- 用ros::Timer的回调函数来替代ros::Rate对象。
- 使用rviz使数据可视化,机器人传感器获得的数据不仅复杂而且通常带有噪声, ROS提供了一个叫做rviz的非常强大的图形界面工具,它可以通过订阅用户选择的话题来显示机器人内部的各种信息,便于机器人的开发和调试。
- 创建消息和服务类型。
- 使用tf工具来管理多个坐标系,用不同的坐标系来描述机器人不同部件的位置,包括机器人要避开或交互的目标。
- 需要知道将某个坐标从一个坐标系到另一个坐标系的变换矩阵(Transforation),ROS提供了一个标准功能包,来帮助节点来完成坐标转换。
- tf具有很强的鲁棒性,既能够处理来自不同节点的数据,也能应对坐标系的实时变换。
- 使用Gazebo仿真, ROS系统最大的优势之一就是能够实现软件的模块化设计,基于这个框架可以轻易地替换系统中的各种软件模块,从而节约系统的开发时间,也使得测试变得简单方便。
- Gazebo是一个高保真的机器人仿真器。
- 在Gazebo中,我们可以建立机器人和相应场景的仿真模型,然后为仿真机器人定义与实体机器人相同的通信接口。