# ROS2 插件
## 相关链接
参考示例:
- {{ '[ros2_plugin]({}/src/examples/plugins/ros2_plugin)'.format(code_site_root_path_url) }}
## 插件概述
**ros2_plugin**是一个基于 [ROS2 Humble](https://docs.ros.org/en/humble/index.html) 实现的通信传输插件,此插件提供了以下组件:
- `ros2`类型 RPC 后端
- `ros2`类型 Channel 后端
插件的配置项如下:
| 节点 | 类型 | 是否可选| 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| node_name | string | 必选 | "" | ROS2 节点名称 |
| executor_type | string | 可选 | "MultiThreaded" | ROS2 执行器类型,可选值:"SingleThreaded"、"StaticSingleThreaded"、"MultiThreaded"|
| executor_thread_num | int | 可选 | 2 | 当 executor_type == "MultiThreaded" 时,表示 ROS2 执行器的线程数 |
关于**ros2_plugin**的配置,使用注意点如下:
- `node_name`表示 ROS2 节点名称,在外界看来,加载了 ROS2 插件的 AimRT 节点就是一个 ROS2 节点,它的 node 名称就是根据此项来配置。
- `executor_type`表示 ROS2 节点执行器的类型,当前有三种选择:`SingleThreaded`、`StaticSingleThreaded`、`MultiThreaded`,具体的含义请参考 ROS2 Humble 的文档。
- `executor_thread_num`仅在`executor_type`值为`MultiThreaded`时生效,表示 ROS2 的线程数。
此外,在使用**ros2_plugin**时,Channel 订阅回调、RPC Server 处理、RPC Client 返回时,使用的都是**ros2_plugin**提供的执行器,当使用者在回调中阻塞了线程时,有可能导致**ros2_plugin**线程池耗尽,从而无法继续接收/发送消息。正如 Module 接口文档中所述,一般来说,如果回调中的任务非常轻量,那就可以直接在回调里处理;但如果回调中的任务比较重,那最好调度到其他专门执行任务的执行器里处理。
以下是一个简单的示例:
```yaml
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ./libaimrt_ros2_plugin.so
options:
node_name: example_ros2_node
executor_type: MultiThreaded
executor_thread_num: 4
```
## ros2 类型 RPC 后端
`ros2`类型的 RPC 后端是**ros2_plugin**中提供的一种 RPC 后端,用于通过 ROS2 RPC 的方式来调用和处理 AimRT RPC 请求。其所有的配置项如下:
| 节点 | 类型 | 是否可选 | 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| timeout_executor | string | 可选 | "" | Client 端 RPC 超时情况下的执行器 |
| clients_options | array | 可选 | [] | 客户端发起 RPC 请求时的规则 |
| clients_options[i].func_name | string | 必选 | "" | RPC Func 名称,支持正则表达式 |
| clients_options[i].qos | map | 可选 | - | QOS 配置 |
| clients_options[i].qos.history | string | 可选 | "default" | QOS 的历史记录选项
keep_last:保留最近的记录(缓存最多 N 条记录,可通过队列长度选项来配置)
keep_all:保留所有记录(缓存所有记录,但受限于底层中间件可配置的最大资源)
default:使用系统默认 |
| clients_options[i].qos.depth | int | 可选 | 10 | QOS 的队列深度选项(只能与 Keep_last 配合使用) |
| clients_options[i].qos.reliability | string | 可选 | "default" | QOS 的可靠性选项
reliable:可靠的(消息丢失时,会重新发送,反复重传以保证数据传输成功)
best_effort:尽力而为的(尝试传输数据但不保证成功传输,当网络不稳定时可能丢失数据)
default:系统默认 |
| clients_options[i].qos.durability | string | 可选 | "default" | QOS 的持续性选项
transient_local:局部瞬态(发布器为晚连接(late-joining)的订阅器保留数据)
volatile:易变态(不保留任何数据)
default:系统默认 |
| clients_options[i].qos.deadline | int | 可选 | -1 | QOS 的后续消息发布到主题之间的预期最大时间量选项
需填毫秒级时间间隔,填 -1 为不设置,按照系统默认 |
| clients_options[i].qos.lifespan | int | 可选 | -1 | QOS 的消息发布和接收之间的最大时间量(单位毫秒)选项
而不将消息视为陈旧或过期(过期的消息被静默地丢弃,并且实际上从未被接收
填-1保持系统默认 不设置 |
| clients_options[i].qos.liveliness | string | 可选 | "default" | QOS 的如何确定发布者是否活跃选项
automatic:自动(ROS2 会根据消息发布和接收的时间间隔来判断)
manual_by_topic:需要发布者定期声明
default:保持系统默认 |
| clients_options[i].qos.liveliness_lease_duration | int | 可选 | -1 | QOS 的活跃性租期的时长(单位毫秒)选项,如果超过这个时间发布者没有声明活跃,则被认为是不活跃的
填-1保持系统默认 不设置 |
| servers_options | array | 可选 | [] | 服务端接收处理 RPC 请求时的规则 |
| servers_options[i].func_name | string | 必选 | "" | RPC Func 名称,支持正则表达式 |
| servers_options[i].qos | map | 可选 | - | QOS 配置 |
| servers_options[i].qos.history | string | 可选 | "default" | QOS 的历史记录选项
keep_last:保留最近的记录(缓存最多 N 条记录,可通过队列长度选项来配置)
keep_all:保留所有记录(缓存所有记录,但受限于底层中间件可配置的最大资源)
default:使用系统默认 |
| servers_options[i].qos.depth | int | 可选 | 10 | QOS 的队列深度选项(只能与 Keep_last 配合使用) |
| servers_options[i].qos.reliability | string | 可选 | "default" | QOS 的可靠性选项
reliable:可靠的(消息丢失时,会重新发送,反复重传以保证数据传输成功)
best_effort:尽力而为的(尝试传输数据但不保证成功传输,当网络不稳定时可能丢失数据)
default:系统默认 |
| servers_options[i].qos.durability | string | 可选 | "default" | QOS 的持续性选项
transient_local:局部瞬态(发布器为晚连接(late-joining)的订阅器保留数据)
volatile:易变态(不保留任何数据)
default:系统默认 |
| servers_options[i].qos.deadline | int | 可选 | -1 | QOS 的后续消息发布到主题之间的预期最大时间量选项
需填毫秒级时间间隔,填 -1 为不设置,按照系统默认 |
| servers_options[i].qos.lifespan | int | 可选 | -1 | QOS 的消息发布和接收之间的最大时间量(单位毫秒)选项
而不将消息视为陈旧或过期(过期的消息被静默地丢弃,并且实际上从未被接收
填-1保持系统默认 不设置 |
| servers_options[i].qos.liveliness | string | 可选 | "default" | QOS 的如何确定发布者是否活跃选项
automatic:自动(ROS2 会根据消息发布和接收的时间间隔来判断)
manual_by_topic:需要发布者定期声明
default:保持系统默认 |
| servers_options[i].qos.liveliness_lease_duration | int | 可选 | -1 | QOS 的活跃性租期的时长(单位毫秒)选项,如果超过这个时间发布者没有声明活跃,则被认为是不活跃的
填 -1 保持系统默认 不设置 |
以下是一个简单的客户端的示例:
```yaml
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ./libaimrt_ros2_plugin.so
options:
node_name: example_ros2_client_node
executor_type: MultiThreaded
executor_thread_num: 4
rpc:
backends:
- type: ros2
options:
clients_options:
- func_name: "(.*)"
qos:
history: keep_last
depth: 10
reliability: reliable
durability: volatile
deadline: -1
lifespan: -1
liveliness: automatic
liveliness_lease_duration: -1
clients_options:
- func_name: "(.*)"
enable_backends: [ros2]
```
以下则是一个简单的服务端的示例:
```yaml
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ./libaimrt_ros2_plugin.so
options:
node_name: example_ros2_server_node
executor_type: MultiThreaded
executor_thread_num: 4
rpc:
backends:
- type: ros2
options:
servers_options:
- func_name: "(.*)"
qos:
history: keep_last
depth: 10
reliability: reliable
durability: volatile
deadline: -1
lifespan: -1
liveliness: automatic
liveliness_lease_duration: -1
servers_options:
- func_name: "(.*)"
enable_backends: [ros2]
```
以上示例中,Server 端启动了一个 ROS2 节点`example_ros2_server_node`,Client 端则启动了一个 ROS2 节点`example_ros2_client_node`,Client 端通过 ROS2 的后端发起 RPC 调用,Server 端通过 ROS2 后端接收到 RPC 请求并进行处理。
Client 端向 Server 端发起调用时,如果协议层是原生 ROS2 协议,那么通信时将完全复用 ROS2 的原生协议,原生 ROS2 节点可以基于该协议无缝与 AimRT 节点对接。
如果 Client 端向 Server 端发起调用时,协议层没有使用 ROS2 协议,那么通信时将基于{{ '[RosRpcWrapper.srv]({}/src/protocols/plugins/ros2_plugin_proto/srv/RosRpcWrapper.srv)'.format(code_site_root_path_url) }}这个 ROS2 协议进行包装,该协议内容如下:
```
string serialization_type
string[] context
byte[] data
---
int64 code
string serialization_type
byte[] data
```
如果此时原生的 ROS2 节点需要和 AimRT 的节点对接,原生 ROS2 节点的开发者需要搭配此协议 Req/Rsp 的 data 字段来序列化/反序列化真正的请求包/回包。
此外,由于 ROS2 对`service_name`有一些特殊要求,AimRT 与ROS2 互通时的`service_name`由 AimRT Func 根据一个类似于 URL 编码的规则来生成:
- 将所有除数字、字母和'/'的符号的 ascii 码以 HEX 编码,加上 '_' 作为前缀。
例如,如果 AimRT Func 名称为`/aaa.bbb.ccc/ddd`,则编码后的 ROS2 service name 就是`/aaa_2Ebbb_2Eccc/ddd`。具体值也会在 ros2_plugin 启动时打印出来。
基于以上特性,`ros2`类型的 RPC 后端可以用于打通与原生 ROS2 节点的 RPC 链路,从而实现 AimRT 对 ROS2 的兼容。
## ros2 类型 Channel 后端
`ros2`类型的 Channel 后端是**ros2_plugin**中提供的一种 Channel 后端,用于通过 ROS2 Topic 的方式来发布和订阅 AimRT Channel 消息。其所有的配置如下:
| 节点 | 类型 | 是否可选| 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| pub_topics_options | array | 可选 | [] | 发布 Topic 时的规则 |
| pub_topics_options[i].topic_name | string | 必选 | "" | Topic 名称,支持正则表达式 |
| pub_topics_options[i].qos | map | 可选 | - | QOS 配置 |
| pub_topics_options[i].qos.history | string | 可选 | "default" | QOS 的历史记录选项
keep_last:保留最近的记录(缓存最多 N 条记录,可通过队列长度选项来配置)
keep_all:保留所有记录(缓存所有记录,但受限于底层中间件可配置的最大资源)
default:使用系统默认 |
| pub_topics_options[i].qos.depth | int | 可选 | 10 | QOS 的队列深度选项(只能与Keep_last配合使用) |
| pub_topics_options[i].qos.reliability | string | 可选 | "default" | QOS 的可靠性选项
reliable:可靠的(消息丢失时,会重新发送,反复重传以保证数据传输成功)
best_effort:尽力而为的(尝试传输数据但不保证成功传输,当网络不稳定时可能丢失数据)
default:系统默认 |
| pub_topics_options[i].qos.durability | string | 可选 | "default" | QOS 的持续性选项
transient_local:局部瞬态(发布器为晚连接(late-joining)的订阅器保留数据)
volatile:易变态(不保留任何数据)
default:系统默认 |
| pub_topics_options[i].qos.deadline | int | 可选 | -1 | QOS 的后续消息发布到主题之间的预期最大时间量选项
需填毫秒级时间间隔,填 -1 为不设置,按照系统默认 |
| pub_topics_options[i].qos.lifespan | int | 可选 | -1 | QOS 的消息发布和接收之间的最大时间量(单位毫秒)选项
而不将消息视为陈旧或过期(过期的消息被静默地丢弃,并且实际上从未被接收
填-1保持系统默认 不设置 |
| pub_topics_options[i].qos.liveliness | string | 可选 | "default" | QOS 的如何确定发布者是否活跃选项
automatic:自动(ROS2 会根据消息发布和接收的时间间隔来判断)
manual_by_topic:需要发布者定期声明
default:保持系统默认 |
| pub_topics_options[i].qos.liveliness_lease_duration | int | 可选 | -1 | QOS 的活跃性租期的时长(单位毫秒)选项,如果超过这个时间发布者没有声明活跃,则被认为是不活跃的
填-1保持系统默认 不设置 |
| sub_topics_options | array | 可选 | [] | 订阅 Topic 时的规则 |
| sub_topics_options[i].topic_name | string | 必选 | "" | Topic 名称,支持正则表达式 |
| sub_topics_options[i].qos | map | 可选 | - | QOS 配置 |
| sub_topics_options[i].qos.history | string | 可选 | "default" | QOS 的历史记录选项
keep_last:保留最近的记录(缓存最多 N 条记录,可通过队列长度选项来配置)
keep_all:保留所有记录(缓存所有记录,但受限于底层中间件可配置的最大资源)
default:使用系统默认 |
| sub_topics_options[i].qos.depth | int | 可选 | 10 | QOS 的队列深度选项(只能与Keep_last配合使用) |
| sub_topics_options[i].qos.reliability | string | 可选 | "default" | QOS 的可靠性选项
reliable:可靠的(消息丢失时,会重新发送,反复重传以保证数据传输成功)
best_effort:尽力而为的(尝试传输数据但不保证成功传输,当网络不稳定时可能丢失数据)
default:系统默认 |
| sub_topics_options[i].qos.durability | string | 可选 | "default" | QOS 的持续性选项
transient_local:局部瞬态(发布器为晚连接(late-joining)的订阅器保留数据)
volatile:易变态(不保留任何数据)
default:系统默认 |
| sub_topics_options[i].qos.deadline | int | 可选 | -1 | QOS 的后续消息发布到主题之间的预期最大时间量选项
需填毫秒级时间间隔,填 -1 为不设置,按照系统默认 |
| sub_topics_options[i].qos.lifespan | int | 可选 | -1 | QOS 的消息发布和接收之间的最大时间量(单位毫秒)选项
而不将消息视为陈旧或过期(过期的消息被静默地丢弃,并且实际上从未被接收
填-1保持系统默认 不设置 |
| sub_topics_options[i].qos.liveliness | string | 可选 | "default" | QOS 的如何确定发布者是否活跃选项
automatic:自动(ROS2 会根据消息发布和接收的时间间隔来判断)
manual_by_topic:需要发布者定期声明
default:保持系统默认 |
| sub_topics_options[i].qos.liveliness_lease_duration | int | 可选 | -1 | QOS 的活跃性租期的时长(单位毫秒)选项,如果超过这个时间发布者没有声明活跃,则被认为是不活跃的
填 -1 保持系统默认 不设置 |
以下是一个简单的发布端的示例:
```yaml
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ./libaimrt_ros2_plugin.so
options:
node_name: example_ros2_pub_node
executor_type: MultiThreaded
executor_thread_num: 4
channel:
backends:
- type: ros2
options:
pub_topics_options:
- topic_name: "(.*)"
qos:
history: keep_last
depth: 10
reliability: reliable
durability: volatile
deadline: -1
lifespan: -1
liveliness: automatic
liveliness_lease_duration: -1
pub_topics_options:
- topic_name: "(.*)"
enable_backends: [ros2]
```
以下则是一个简单的订阅端的示例:
```yaml
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ./libaimrt_ros2_plugin.so
options:
node_name: example_ros2_sub_node
executor_type: MultiThreaded
executor_thread_num: 4
channel:
backends:
- type: ros2
options:
sub_topics_options:
- topic_name: "(.*)"
qos:
history: keep_last
depth: 10
reliability: reliable
durability: volatile
deadline: -1
lifespan: -1
liveliness: automatic
liveliness_lease_duration: -1
sub_topics_options:
- topic_name: "(.*)"
enable_backends: [ros2]
```
以上示例中,发布端启动了一个 ROS2 节点`example_ros2_pub_node`,订阅端则启动了一个 ROS2 节点`example_ros2_sub_node`,发布端通过 ROS2 的后端发布消息,订阅端通过 ROS2 后端接收到消息并进行处理。
如果消息发布订阅时,协议层是原生 ROS2 协议,那么通信时将完全复用 ROS2 的原生协议,原生 ROS2 节点可以基于该协议无缝与 AimRT 节点对接。
如果消息发布订阅时,协议层没有使用 ROS2 协议,那么通信时将基于{{ '[RosMsgWrapper.msg]({}/src/protocols/plugins/ros2_plugin_proto/msg/RosMsgWrapper.msg)'.format(code_site_root_path_url) }}这个 ROS2 协议进行包装,该协议内容如下:
```
string serialization_type
string[] context
byte[] data
```
如果此时原生的 ROS2 节点需要和 AimRT 的节点对接,原生 ROS2 节点的开发者需要搭配此协议的 data 字段来序列化/反序列化真正的消息。
此外,AimRT 与 ROS2 互通时的`Topic`名称由以下规则生成:`${aimrt_topic}/${ros2_encode_aimrt_msg_type}`。其中`${aimrt_topic}`是 AimRT Topic 名称,`${ros2_encode_aimrt_msg_type}`根据 AimRT Msg 名称由一个类似于 URL 编码的规则来生成:
- 将所有除数字、字母和'/'的符号的 ascii 码以 HEX 编码,加上 '_' 作为前缀。
例如,如果 AimRT Topic 名称是`test_topic`,AimRT Msg 名称为`pb:aaa.bbb.ccc`,则最终 ROS2 Topic 值就是`test_topic/pb_3Aaaa_2Ebbb_2Eccc`。具体值也会在 ros2_plugin 启动时打印出来。
基于这个特性,`ros2`类型的 Channel 后端可以用于打通与原生 ROS2 节点的 Channel 链路,从而实现 AimRT 对 ROS2 的兼容。
开发者还可以参考{{ '[ros2_plugin]({}/src/examples/plugins/ros2_plugin)'.format(code_site_root_path_url) }}中的示例,与原生 ros2 humble 节点进行通信。