ROS 2 是一个基于强类型、匿名发布/订阅机制的中间件,它允许在不同进程之间传递消息。
任何 ROS 2 系统的核心都是 ROS 图。ROS 图是指 ROS 系统中的节点网络以及它们之间进行通信的连接。
这些概念将帮助您开始了解 ROS 2 的基础知识。
节点是 ROS 2 图中的参与者,它使用客户端库与其他节点通信。节点可以与同一进程内、不同进程内或不同机器上的其他节点通信。节点通常是 ROS 图中的计算单元;每个节点应该做一件合乎逻辑的事情。
节点可以发布到命名主题以将数据传递给其他节点,或者订阅命名主题以从其他节点获取数据。它们还可以充当服务客户端,让另一个节点代表它们执行计算,或者充当服务服务器,为其他节点提供功能。对于长时间运行的计算,节点可以充当操作客户端,让另一个节点代表它们执行,也可以充当操作服务器,为其他节点提供功能。节点可以提供可配置参数,以在运行时更改行为。
节点通常是同时包含发布者、订阅者、服务服务器、服务客户端、操作服务器和操作客户端的复杂组合。
节点之间的连接是通过分布式发现过程建立的。
节点的发现通过 ROS 2 的底层中间件自动进行。它可以总结如下:
只有具有兼容服务质量设置的节点才会与其他节点建立连接。以“说话者-倾听者”演示为例。在一个终端中运行 C++ talker 节点将发布有关某个主题的消息,而在另一个终端中运行 Python listener 节点将订阅有关该主题的消息。
您应该看到这些节点自动发现彼此,并开始交换消息。
ROS 应用程序通常通过三种类型之一的接口进行通信:主题、服务或操作。ROS 2 使用一种简化的描述语言,即接口定义语言 ( IDL ) 来描述这些接口。这种描述使得 ROS 工具可以轻松地为几种目标语言中的接口类型自动生成源代码。
在本文档中,我们将描述支持的类型:
.msg
文件是描述 ROS 消息字段的简单文本文件。它们用于为不同语言的消息生成源代码。.srv
文件描述服务。它们由两部分组成:请求和响应。请求和响应是消息声明。.action
文件描述动作。它们由三部分组成:目标、结果和反馈。每个部分本身就是一个消息声明。消息是 ROS 2 节点在网络上向其他 ROS 节点发送数据的一种方式,无需响应。例如,如果一个 ROS 2 节点从传感器读取 Temperature
数据,那么它可以使用 Temperature
消息在 ROS 2 网络上发布该数据。ROS 2 网络上的其他节点可以订阅该数据并接收温度消息。
消息在 ROS 包的 msg/
目录中的 .msg
文件中描述和定义。.msg
文件由两部分组成:字段和常量。
每个字段由类型和名称组成,用空格分隔,即:
fieldtype1 fieldname1
fieldtype2 fieldname2
fieldtype3 fieldname3
例如:
int32 my_int
string my_string
字段类型可以是:
当前支持的内置类型:
Type name | C++ | Python | DDS type |
---|---|---|---|
bool | bool | builtins.bool | boolean |
byte | uint8_t | builtins.bytes* | octet |
char | char | builtins.str* | char |
float32 | float | builtins.float* | float |
float64 | double | builtins.float* | double |
int8 | int8_t | builtins.int* | octet |
uint8 | uint8_t | builtins.int* | octet |
int16 | int16_t | builtins.int* | short |
uint16 | uint16_t | builtins.int* | unsigned short |
int32 | int32_t | builtins.int* | long |
uint32 | uint32_t | builtins.int* | unsigned long |
int64 | int64_t | builtins.int* | long long |
uint64 | uint64_t | builtins.int* | unsigned long long |
string | std::string | builtins.str | string |
wstring | std::u16string | builtins.str | wstring |
每个内置类型都可以用来定义数组:
Type name | C++ | Python | DDS type |
---|---|---|---|
static array | std::array |
builtins.list* | T[N] |
unbounded dynamic array | std::vector | builtins.list | sequence |
bounded dynamic array | custom_class |
builtins.list* | sequence |
bounded string | std::string | builtins.str* | string |
所有比其 ROS 定义更宽松的类型都通过软件在范围和长度上强制 ROS 约束。
使用数组和有界类型定义消息的示例:
int32[] unbounded_integer_array
int32[5] five_integers_array
int32[<=5] up_to_five_integers_array
string string_of_unbounded_size
string<=10 up_to_ten_characters_string
string[<=5] up_to_five_unbounded_strings
string<=10[] unbounded_array_of_strings_up_to_ten_characters_each
string<=10[<=5] up_to_five_strings_up_to_ten_characters_each
字段名称必须是小写字母数字字符和下划线分隔单词。必须以字母开头,不能以下划线结尾,也不能有两个连续的下划线。
默认值可以设置为消息类型中的任何字段。目前默认值不支持字符串数组和复杂类型(即在上面的内置类型表中不存在的类型;这适用于所有嵌套的消息)。
通过在字段定义行中添加第三个元素来定义默认值,即:
fieldtype fieldname fielddefaultvalue
例如:
uint8 x 42
int16 y -2000
string full_name "John Doe"
int32[] samples [-200, -100, 0, 100, 200]
注意:字符串值必须在单’或双’引号中定义。当前字符串值不能转义
每个常量的定义就像一个有默认值的字段描述,只不过这个值永远不能通过编程改变。这个值的赋值使用等于 ’ = ’ 符号表示,例如:
constanttype CONSTANTNAME=constantvalue
例如:
int32 X=123
int32 Y=-123
string FOO="foo"
string EXAMPLE='bar'
注意:常量名称必须大写
服务是一种请求/响应通信,客户端(请求者)等待服务器(响应者)进行短时间的计算并返回结果。
服务在 ROS 包的 srv/
目录中的 .srv
文件中描述和定义。
服务描述文件由请求和响应 msg 类型组成,之间用 ---
分隔。任何两个用 ---
连接起来的。msg 文件都是合法的服务描述。
下面是一个非常简单的服务示例,它接受一个字符串并返回一个字符串:
string str
---
string str
当然,我们可以变得更复杂 (如果你想引用来自同一个包的消息,则不能提及包名) :
# request constants
int8 FOO=1
int8 BAR=2
# request fields
int8 foobar
another_pkg/AnotherMessage msg
---
# response constants
uint32 SECRET=123456
# response fields
another_pkg/YetAnotherMessage val
CustomMessageDefinedInThisPackage value
uint32 an_integer
注意:您不能在服务中嵌入另一个服务。
动作是一个长期运行的请求/响应通信,其中动作客户端 (请求者) 等待动作服务器 (响应者) 采取一些动作并返回结果。与服务相比,动作可以长时间运行 (几秒或几分钟),在动作发生时提供反馈,并且可以被中断。动作定义的形式如下:
<request_type> <request_fieldname>
---
<response_type> <response_fieldname>
---
<feedback_type> <feedback_fieldname>
与服务类似,请求字段在第一个三划线 (---
) 之前,响应字段在第一个三划线 (---
) 之后。在第二个三线字符之后还有第三组字段,这是发送反馈时要发送的字段。
可以有任意数量的请求字段(包括零),任意数量的响应字段(包括零),任意数量的反馈字段(包括零)。
遵循与消息的
相同的规则。
遵循与消息的
相同的规则。例如,斐波那契动作定义包含以下内容:
int32 order
---
int32[] sequence
---
int32[] sequence
这是一个动作定义,动作客户端发送一个 int32 字段,表示要执行的斐波那契数列的数量,并期望动作服务器生成一个包含完整步骤的 int32 数组。在这个过程中,动作服务器还可以提供一个 int32 的中间数组,其中包含到某个点为止完成的步骤。
Topics 是 ROS 2 提供的三个主要接口样式之一。话题应该用于连续的数据流,如传感器数据、机器人状态等。如前所述,ROS 2 是一个强类型的匿名发布/订阅系统。让我们分解这个句子并进行更多的解释。
发布/订阅系统中有数据的生产者(发布者)和数据的消费者(订阅者)。
发布者和订阅者通过“话题”的概念知道如何联系彼此,这是一个通用的名称,以便实体可以找到彼此。例如,创建发布者时,还必须提供一个字符串,表示主题的名称;订阅者也是如此。具有相同主题名称的任何发布者和订阅者可以直接相互通信。
对于任何特定的主题,发布者可以是零个或多个,订阅者可以是零个或多个。当任何一个发布者将数据发布到主题时,系统中的所有订阅者都将收到该数据。这个系统也被称为“总线”,因为它有点像电子工程中的设备总线。这种总线的概念是使 ROS 2 成为一个强大而灵活的系统的一部分。发布者和订阅者可以根据需要来去自如,这意味着调试和自省是系统的自然扩展。例如,如果需要记录数据,可以使用 ros2 bag record 命令。在底层,ros2 bag record 为您告诉它的任何主题创建一个新的订阅者,而不会中断到系统其他部分的数据流。
介绍中提到的另一个事实是 ROS 2 是“匿名的”。这意味着,当订阅者获得一条数据时,它通常不知道或不关心最初是哪个发布者发送了它(尽管它可以知道它是否想要)。这种架构的好处是发布者和订阅者可以随意交换,而不会影响系统的其他部分。
最后,介绍中还提到发布/订阅系统是“强类型”的。在这种情况下,它有两个含义:
uint32 field1
string field2
然后代码将确保 field 始终是无符号整数,field2 始终是字符串。在 ROS 2 中,服务指的是远程过程调用。换句话说,一个节点可以对另一个节点进行远程过程调用,该节点将进行计算并返回结果。
此结构反映在服务消息定义的外观中:
uint32 request
---
uint32 response
在 ROS 2 中,服务期望快速返回,因为客户端通常在等待结果。服务永远不应该用于长时间运行的进程,特别是可能需要为异常情况抢占的进程。如果你有一个要进行长时间计算的服务,可以考虑使用 action。
服务由服务名称标识,它看起来很像主题名称(但在不同的命名空间中)。
服务由两部分组成:服务服务器和服务客户端。
服务服务器是接受远程过程请求并在其上执行某些计算的实体。例如,假设 ROS 2 消息包含以下内容:
uint32 a
uint32 b
---
uint32 sum
服务服务器将是接收此消息,将 a 和 b 相加并返回 sum 的实体。
注意:每个服务名应该只有一个服务服务器。当多个服务服务器使用相同的服务名称时,哪个服务服务器接收客户端请求未定义。
服务客户端是一个实体,它将请求远程服务服务器代表它执行计算。从上面的示例来看,服务客户端是创建包含 a 和 b 的初始消息,并等待服务服务器计算和并返回结果的实体。与服务服务器不同的是,可以有任意数量的服务客户机使用相同的服务名称。
在 ROS 2 中,操作是指具有反馈和取消或抢占目标能力的长时间运行的远程过程调用。例如,运行机器人的高级状态机可能会调用一个动作来告诉导航子系统前往一个路点,这可能需要几秒钟(或几分钟)的时间。在这个过程中,导航子系统可以提供关于它已经走了多远的反馈,高级状态机可以选择取消或抢占到那个航路点的行程。
该结构反映在操作消息定义的外观中:
int32 request
---
int32 response
---
int32 feedback
在 ROS 2 中,动作预计是长时间运行的过程,因为在设置和监视连接时存在开销。如果需要短时间运行的远程过程调用,请考虑使用服务。动作由操作名称标识,它看起来很像主题名称(但在不同的命名空间中)。一个动作由两部分组成:动作服务器和动作客户端。
动作服务器是将接受远程过程请求并在其上执行某些过程的实体。它还负责在操作进行时发送反馈,并应对取消/抢占请求作出反应。
例如,考虑一个操作,它使用以下接口计算斐波那契数列:
int32 order
---
int32[] sequence
---
int32[] sequence
动作服务器是接收此消息,开始按顺序计算序列(在此过程中提供反馈),并最终按顺序返回完整结果的实体。
注意:每个操作名称应该只有一个操作服务器。在相同操作名称上的多个操作服务器的情况下,哪个操作服务器将接收客户端请求是未定义的。
动作客户端是一个实体,它将请求远程动作服务器代表它执行一个过程。按照上面的示例,动作客户端是创建包含订单的初始消息,并等待动作服务器计算序列并返回它(在此过程中提供反馈)的实体。与操作服务器不同,可以有任意数量的动作客户端使用相同的操作名称。
ROS 2 中的参数与单个节点相关联。参数用于在启动时(和运行时)配置节点,而无需更改代码。参数的生命周期与节点的生命周期相关(不过节点可以实现某种持久性,以便在重启后重新加载参数值)。
参数按节点名称、节点命名空间、参数名称和参数命名空间进行命名。提供参数命名空间是可选的。
每个参数由一个键、一个值和一个描述符组成。键是字符串,值是以下类型之一:bool、int64、float64、string、byte[]、bool[]、int64[]、float64[] 或 string[]
。默认情况下,所有描述符都是空的,但可以包含参数描述、值范围、类型信息和附加约束。
有关 ROS 参数的实践教程,请参阅了解参数。
默认情况下,节点需要声明在其生命周期内接受的所有参数。这样就可以在节点启动时明确定义参数的类型和名称,从而减少以后配置错误的机会。有关声明和使用节点参数的教程,请参见在类中使用参数 (C++) 或在类中使用参数 (Python)。
对于某些类型的节点,并非所有参数都能提前知道。在这种情况下,可以将允许未声明参数(allow_undeclared_parameters
)设置为 "true
"来实例化节点,这样即使没有声明参数,也可以在节点上获取和设置参数。
ROS 2 节点上的每个参数都有一个 "概述 "中提到的预定义参数类型。默认情况下,在运行时更改已声明参数类型的尝试会失败。这就避免了一些常见错误,例如将布尔值放入整数参数。
如果参数需要多种不同类型,且使用该参数的代码可以处理,则可以更改默认行为。声明参数时,应使用参数描述符(ParameterDescriptor
),并将成员变量 dynamic_typing
设为 true
。
ROS 2 节点可以注册三种不同类型的回调,以便在参数发生变化时获得通知。这三种回调都是可选的。
第一种回调称为 "预设参数 "回调,可通过节点 API 调用 add_pre_set_parameters_callback
进行设置。该回调将传递一个正在更改的 Parameter
对象列表,并且不返回任何内容。调用时,它可以修改 Parameter
列表,以更改、添加或删除条目。举例来说,如果 Parameter1
发生变化时, Parameter2
也要随之变化,那么就可以通过这个回调来实现。
第二个回调称为 "设置参数 "回调,可通过调用节点 API 中的 add_on_set_parameters_callback
来设置。回调会传递一个不可变 Parameter
对象列表,并返回一个 rcl_interfaces/msg/SetParametersResult
。该回调的主要目的是让用户能够检查即将发生的参数变更,并明确拒绝该变更。
重要的是,"设置参数 "回调不会产生任何副作用。由于可以连锁多个 "设置参数 "回调,因此单个回调无法知道后面的回调是否会拒绝更新。例如,如果单个回调对它所在的类进行了更改,它可能会与实际参数不同步。要在成功更改参数后获取回调,请参阅下面的下一类回调。
第三种类型的回调称为 "后置参数 "回调,可通过调用节点 API 中的 add_post_set_parameters_callback
来设置。回调传递的是一个不可变 Parameter
对象列表,并且不返回任何内容。该回调的主要目的是让用户能够对已成功接受的参数变化做出反应。
ROS 2 节点可通过节点应用程序接口(API)执行参数操作,如在类中使用参数(C++)或在类中使用参数(Python)中所述。外部进程可以通过节点实例化时默认创建的参数服务执行参数操作。默认创建的服务有
/node_name/describe_parameters
: 使用 rcl_interfaces/srv/DescribeParameters
服务类型。给定参数名称列表,返回与参数相关的描述符列表。
/node_name/get_parameter_types
: 使用 rcl_interfaces/srv/GetParameterTypes
服务类型。给定参数名列表,返回与参数相关的参数类型列表。
/node_name/get_parameters
: 使用 rcl_interfaces/srv/GetParameters
服务类型。给定参数名列表,返回与参数相关的参数值列表。
/node_name/list_parameters
: 使用 rcl_interfaces/srv/ListParameters
服务类型。如果给定一个可选的参数前缀列表,则返回带有该前缀的可用参数列表。如果前缀为空,则返回所有参数。
/node_name/set_parameters
: 使用 rcl_interfaces/srv/SetParameters
服务类型。给定参数名称和值的列表,尝试在节点上设置参数。返回每个参数的设置结果列表;其中有些可能成功,有些可能失败。
/node_name/set_parameters_atomically
: 使用 rcl_interfaces/srv/SetParametersAtomically
服务类型。给定参数名和值的列表,尝试在节点上设置参数。从尝试设置所有参数的结果中返回一个结果,因此如果一个参数设置失败,则所有参数均失败。
运行节点时,可以通过单个命令行参数或 YAML 文件设置初始参数值。有关如何设置初始参数值的示例,请参见直接从命令行设置参数。
通过 ROS 2 启动工具运行节点时,也可以设置初始参数值。有关如何通过启动指定参数的信息,请参阅本文档。
ros2 param
命令是与已运行节点的参数交互的一般方法。ros2 param
使用上述参数服务 API 来执行各种操作。有关如何使用 ros2 param
的详情,请参阅本使用指南。
启动文件迁移指南说明了如何将 param
和 rosparam
启动标记从 ROS 1 迁移到 ROS 2。
YAML 参数文件迁移指南介绍了如何将参数文件从 ROS 1 迁移到 ROS 2。
在 ROS 1 中,roscore
就像一个全局参数黑板,所有节点都可以在这里获取和设置参数。由于 ROS 2 中没有中央 roscore,因此这一功能已不复存在。在 ROS 2 中,推荐的方法是使用与使用节点密切相关的节点参数。如果仍然需要全局黑板,可以为此创建一个专用节点。ROS 2 的 ros-iron-demo-nodes-cpp
软件包中就包含了一个名为 parameter_blackboard
的黑板:
ros2 run demo_nodes_cpp parameter_blackboard
参数黑板的代码在这里。
如果觉得内容不错,请点赞、收藏、关注