ROS2 插件

相关链接

参考示例:

插件概述

ros2_plugin是一个基于 ROS2 Humble 实现的通信传输插件,此插件提供了以下组件:

  • 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 节点执行器的类型,当前有三种选择:SingleThreadedStaticSingleThreadedMultiThreaded,具体的含义请参考 ROS2 Humble 的文档。

  • executor_thread_num仅在executor_type值为MultiThreaded时生效,表示 ROS2 的线程数。

此外,在使用ros2_plugin时,Channel 订阅回调、RPC Server 处理、RPC Client 返回时,使用的都是ros2_plugin提供的执行器,当使用者在回调中阻塞了线程时,有可能导致ros2_plugin线程池耗尽,从而无法继续接收/发送消息。正如 Module 接口文档中所述,一般来说,如果回调中的任务非常轻量,那就可以直接在回调里处理;但如果回调中的任务比较重,那最好调度到其他专门执行任务的执行器里处理。

以下是一个简单的示例:

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 (可以看到把 “:” 及之前的 <msg_type> 去掉并且将非数字、字母和’/’的符号的 ascii 码以 HEX 编码,加上 ‘_’ 作为前缀)。 若需要 remap,则可以配置如下:

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

以下是一个简单的客户端的示例:

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]

以下则是一个简单的服务端的示例:

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这个 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 保持系统默认 不设置

以下是一个简单的发布端的示例:

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]

以下则是一个简单的订阅端的示例:

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这个 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中的示例,与原生 ros2 humble 节点进行通信。