# 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保持系统默认 不设置 | | clients_options[i].remapping_rule | string | 可选 | "" | 用于将 clients_options[i].func_name 所正则匹配到的 func_name 按照新则规则进行重映射, 以生成新的 ros2_func_name
在书写的时候支持替换规则: {j} 表示第 j 个被正则匹配的捕获组 | | 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 保持系统默认 不设置 | | servers_options[i].remapping_rule | string | 可选 | "" | 用于将 servers_options[i].func_name 所正则匹配到的 func_name 按照新则规则进行重映射, 以生成新的 ros2_func_name
在书写的时候支持替换规则: {j} 表示第 j 个被正则匹配的捕获组 | 下面是一个 remap 用法的简单示例:名为 `pb:/aimrt_server/GetFooData` 的 AimRT func_name ,若不需要 remap,则 最终生成的 ros func_name 就是 `/aimrt_5Fserver/GetFooData` (可以看到把 ":" 及之前的 去掉并且将非数字、字母和'/'的符号的 ascii 码以 HEX 编码,加上 '_' 作为前缀)。 若需要 remap,则可以配置如下: ```yaml rpc: backends: - type: ros2 options: servers_options: - func_name: "(.*)/(.*)/(.*)" #这里是填写匹配 AimRT func_name 的正则表达式 remapping_rule: "{1}/{2}" # 这里填写重映射规则,用于生成新的ros2_func_name。 这里也可以简化写成 /{2} ``` 经过该配置,第一个`(.*)`捕获到了 `pb:`, 第二个`(.*)`捕获到了 `aimrt_server`, 第三个`(.*)`捕获到了 `GetFooData`, 最后生成的 ros func_name 就是 `/GetFooData` , 为了简化书写,在选项中的`remapping_rule`可以不填写`{1}`所代表的消息类型,系统会自动生成以适配AimRT func_name 和 ros2 func_name 的转换关系。以下是一些快速的例子,用于演示更丰富的用法,假设 AimRT func_name 为 `pb:/aaa/bbb/ccc` : | func_name | remapping_rule | ros2_func_name | | ----------------------- | --------------- | -------------- | | (.\*)/(.\*)/(.\*)/(.\*) | | /aaa/bbb/ccc | | (.\*)/(.\*)/(.\*)/(.\*) | {1}/{2}/ddd/{4} | /aaa/ddd/ccc | | (.\*)/(.\*)/(.\*)/(.\*) | /{2}/ddd/{4} | /aaa/ddd/ccc | | (.\*)/(.\*)/(bbb)/(.\*) | {1}/{3}_{4} | /bbb_5Fccc | | (.\*)/(.\*)/(bbb)/(.\*) | /{3}/eee | /bbb/eee | 以下是一个简单的客户端的示例: ```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 节点进行通信。