# 仿真机器人MetaEngine
本文面向工程用户，详细说明如何将**基于MetaEngine + ROS2 Gem的仿真机器人端到端接入R2C**，包括：
1. 概念与总体流程。
2. 配置文件逐项解读（r2c_ur5e.yaml为范本）。
3. 配置项含义与取值建议。
4. 端到端接入步骤（环境 \> MetaEngine仿真侧 \> R2C配置 \> 启动 \> 联调验证）。
#### 一、为什么仿真机器人也能接入R2C
R2C并不直接对接某个仿真器，而是对接**标准ROS2接口** 。MetaEngine启用**ROS2 Gem**后，仿真中的机器人会把运动学、传感器、控制器以标准ROS2 Topics/Services/Actions对外暴露。
表1ROS2接口示例 
| 仿真侧（ROS2 Gem）      | ROS2接口                                               | R2C侧消费方式 |
|:---|:---|:---|
| ROS2RobotControl关节状态 | /joint_states（sensor_msgs/JointState）                 | 订阅       |
| ROS2CameraSensor相机 | /xxx/camera_image_color（sensor_msgs/Image）          | 订阅+编码    |
| 关节位置控制器              | /arm_position_controller/commands（Float64MultiArray）  | 发布       |
| 夹爪控制器             | /hand_position_controller/commands（Float64MultiArray） | 发布        |
| 轨迹Action Server     | FollowJointTrajectory Action                         | 动作客户端     |
| TF广播                | /tf、/tf_static                                       | 可选，预留      |
   
因此**R2C的Ros2HardwareAdapter对真实机器人与仿真机器人完全一视同仁**：它只负责"订阅ROS2话题、按配置翻译成R2C观测；把R2C动作翻译回ROS2命令"。仿真与真实的差别只是对端是谁，配置范式完全相同。
相关参考：
- ROS2 Gem：<https://docs.o3de.org/docs/user-guide/gems/reference/robotics/ros2/>
- O3DE机器人交互与项目配置：<https://docs.o3de.org/docs/user-guide/interactivity/robotics/>与<https://docs.o3de.org/docs/user-guide/interactivity/robotics/project-configuration/>
 
#### 二、端到端数据流总览
- **观测上行**：订阅ROS2 \> streams \> device_to_r2c \> Observations \> session.publish_observations() \> Zenoh \> 云端。
- **控制下行**：云端Actions \> Zenoh \> r2c_to_device \> 命令dict \> adapter.send_action() \> 路由到ROS2发布器/服务/动作 \> 仿真机器人执行。
 
#### 三、端到端接入步骤
需要进行如下准备环境、MetaEngine（O3DE）仿真侧准备、编写R2C客户端通信配置、编写仿真机器人硬件配置以及启动并验证。
#### 准备环境
表2环境要求 
| 配置项               | 要求                                       |
|:---|:---|
| 操作系统             | Linux（推荐Ubuntu 22.04 / 24.04）            |
| ROS2            | Humble (22.04) / Jazzy (24.04)            |
| Python          | ≥ 3.10                                   |
| rclpy         | 随ROS2安装                                 |
| message_filters | 可选：pip install message_filters（多路观测时间同步） |
   
执行如下命令，确认ROS2环境：
```
source /opt/ros/${ROS_DISTRO}/setup.bash 
python3 -c "import rclpy; print('rclpy OK')"
```
![](https://support.huaweicloud.com/sdkreference-cloudrobo/public_sys-resources/caution_3.0-zh-cn.png)
R2C端侧进程与MetaEngine仿真进程需在同一ROS2域（ROS_DOMAIN_ID 一致）才能互相发现话题。
#### MetaEngine（O3DE）仿真侧准备
1. 在O3DE工程中启用**ROS2 Gem**（Project Manager \> Gems \> ROS2）。
2. 导入机器人URDF：使用ROS2RobotImporter导入机器人模型，生成物理模型与关节。
3. 为机器人挂载：
   - ROS2RobotControl/控制器 \> 发布/joint_states、暴露关节命令话题。
   
   - ROS2CameraSensor（手/头/外置）\> 发布sensor_msgs/Image。
   
   - 夹爪等执行器 \> Float64MultiArray 命令话题。
    
4. 运行仿真并验证话题可用：
```
ros2 topic list 
ros2 topic echo /joint_states --once
ros2 topic hz /joint_states
```
记录下仿真侧实际发布的**话题名与消息类型**，这是后续配置subscriptions / command_publishers的依据。
#### 编写R2C客户端通信配置
config/client_config.yaml（Zenoh通信）：
```
project_id: "test-tenant"      # 与云端一致
device_id: "sim-ur5e-001"      # 设备标识
client_id: "sim-edge-001"
endpoints: []                  # 留空自动发现，或填 Router 地址
mode: "peer"                   # peer 或 client
protocol: "zenoh"
```
#### 编写仿真机器人硬件配置
hardware.type: "ros2"，配置主体为r2c_ur5e.yaml（详见[四、配置文件逐项详解]）。按仿真侧实际话题名/消息类型调整。
#### 启动并验证
```
source /opt/ros/${ROS_DISTRO}/setup.bash
# 保险起见先用 dry_run 验证链路，再真正驱动
python -m r2c_sdk.cloudroboclient \
  --client-config config/client_config.yaml \
  --robot-config r2c_ur5e.yaml \
  --log-level INFO
```
启动后应看到日志：
- Connecting ROS2 hardware adapter / ROS2 adapter connected (subscriptions=.., publishers=..)。
- 观测正常发布、被云端接收并回推Actions。
- init_joints将机械臂/夹爪摆到初始位。
 
 #### 四、配置文件逐项详解
以下以r2c_ur5e.yaml为主的仿真配置说明。
#### runtime：运行/控制循环
表3配置项说明 
| 配置项                             | 说明                                | 默认/建议        |
|:---|:---|:---|
| publish_hz                    | 动作执行频率，也决定观测发布检查频率。                 | 10--30 Hz       |
| max_duration_s                  | 最大运行时长（秒），0表示无限运行（Ctrl+C停止）。      | 0             |
| dry_run                       | true时不执行Actions，但仍发观测，用于安全联调。      | false         |
| action_response_timeout_s      | 发布观测后等待Action的超时，超时则重发观测。           | ≥ 30（弱网/大模型） |
| max_enqueue_actions_per_chunk | 每个chunk最多入队的Action步数，多余丢弃；-1全部采用。 | -1               |
| enable_action_chunk_alignment | 是否启用chunk对齐，避免轨迹跳变。                  | false            |
| skip_initial_observations     | 启动时跳过的观测数（让动作队列先填满）。               | 1              |
| async_request.enabled          | 异步请求融合开关（一般false）。                | false        |
| keyboard_control.enabled      | 启用键盘控制（空格键：暂停、h键：回home、e键：退出）       | true          |
   
#### hardware：ROS2硬件适配器
```
hardware:
  type: "ros2"
  config:
    node_name: "r2c_ur5e_ros"     # ROS2 节点名（与其他节点不重名）
    spin_timeout_sec: 0.05        # spin_once 超时（秒）
    auto_spin: True               # 是否自动启动 spin 线程
```
表4配置项说明 
| 配置项               | 说明                              |
|:---|:---|
| node_name         | ROS2节点名。**多台仿真机器人应使用不同节点名**，避免冲突。 |
| spin_timeout_sec | spin_once的处理超时，默认0.05s。          |
| auto_spin          | True时SDK自动启动后台spin线程处理回调。        |
   
#### hardware.config.init_joints：启动自摆位
连接成功、发布器就绪后，主动下发初始位（把机械臂/夹爪摆到安全初位）。_publish_init_joints 在 connect() 末尾调用。
```
init_joints:
  enabled: true            # 是否启用
  delay_sec: 3.0           # 连接后延迟多少秒再下发
  entries:
    - publisher: arm       # 对应 command_publishers 的一个发布器
      message:             # 直接构造该发布器类型消息
        data: [0.0, -1.9373155, 1.204277, -0.9075712, 4.694936, -0.0872665]
    - publisher: hand
      message:
        data: [0.0, 0.0]
```
表5配置项说明 
| 配置项                           | 说明                                                               |
|:---|:---|
| enabled                      | 是否在启动时执行初始摆位。                                                      |
| delay_sec                   | 连接后延迟秒数，等待对端控制话题就绪。                                               |
| entries\[\].publisher        | 指向command_publishers中的发布器名。                                        |
| entries\[\].message           | 直接以dict构造该发布器消息（此处为 std_msgs/Float64MultiArray.data）。               |
| entries\[\].joints（替代 message） | 也可提供joints+joint_names，SDK自动构造 trajectory_msgs/JointTrajectory 单点。 |
   
#### hardware.config.subscriptions：观测订阅（上行）
每个订阅项把一个ROS2 Topic拉进streams。一个典型订阅项如下所示：
```
joint_states:                     # 订阅流名称（任意起名，用于 store_as）
  topic: /joint_states
  msg_type: sensor_msgs.msg.JointState
  qos: 10
  max_update_hz: 10               # 限频，避免高频洪泛
  store_as: joint_states          # 在 streams 中的键名
  include_fields: ["name", "position", "velocity", "effort"]
  field_aliases:
    name: names                    # 字段重命名 name > names
  transforms:                      # 订阅级转换链（可选）
    - select_joints_by_name:
        names: [... 8 个关节名 ...]
```
表6配置项说明 
| 配置项              | 说明                                         | 是否必需     |
|:---|:---|:---|
| topic              | ROS2话题名。                               | 是       |
| msg_type        | ROS2消息类型（点分路径，如 sensor_msgs.msg.Image）。 | 是       |
| qos               | QoS 队列深度。                               | 否（默认 10） |
| store_as          | 写入streams的键名（默认用流名）。                      | 否        |
| max_update_hz      | 最大更新频率，用于降采样。                          | 否        |
| include_fields    | 只提取指定字段。                                  | 否        |
| field_aliases   | 字段重命名映射（name\>names）。                   | 否         |
| store_raw_message | true保留原始ROS消息（相机图像需用它）。                  | 否       |
| transforms        | 订阅级转换链（如 select_joints_by_name）。         | 否       |
   
**相机订阅示例**（UR5e）：
```
head:
  topic: /head_camera/camera_image_color
  msg_type: sensor_msgs.msg.Image
  qos: 10
  store_as: head
  store_raw_message: true      # 保留 Image 对象，供下游 ros_image_to_jpeg 使用
```
#### hardware.config.command_publishers：命令发布（下行）
每个发布项把一个R2C命令映射到ROS2Topic：
```
command_publishers:
  arm:                             # 发布器名称（供 target/init_joints 引用）
    topic: "/arm_position_controller/commands"
    msg_type: std_msgs.msg.Float64MultiArray
    qos: 10
  hand:
    topic: "/hand_position_controller/commands"
    msg_type: std_msgs.msg.Float64MultiArray
    qos: 10
default_command_publisher: arm     # 未显式指定时的默认发布器
action_targets:                    # 语义 target > 发布器名
  arm: arm
  hand: hand
```
表7配置项说明 
| 配置项                        | 说明                                        |
|:---|:---|
| topic / msg_type / qos      | 目标话题、消息类型、QoS。                             |
| default_command_publisher | 命令未携带publisher/target时的默认发布器。              |
| action_targets           | 逻辑目标名 \> 发布器名的映射，供r2c_to_device的target解析。 |
   
**下行通道**：
**Topic 发布器**（推荐，低延迟）：command_publishers。
#### translator：数据翻译层
type: "configurable"，由device_to_r2c（ROS2 \> R2C）与r2c_to_device（R2C \> ROS2）两组映射构成。
**device_to_r2c： 组R2C观测**
**关节位置**：
```
- target_key: "joint_states.names"            # 目标标准字段
  default: ["shoulder_pan_joint", "shoulder_lift_joint", "elbow_joint",
            "wrist_1_joint", "wrist_2_joint", "wrist_3_joint", "gripper"]
- target_key: "joint_states.position"         # 来自订阅 streams
  source: streams.joint_states.position
  transforms:
    - array_to_list
    - slice: { start: 0, end: 6 }             # 前 6 维 = 机械臂
- target_key: "joint_states.position"         # 第 7 维 = 夹爪行程
  target_index: 6
  source: streams.joint_states.position
  transforms:
    - array_to_list
    - slice: { start: 6, end: 8 }             # 夹爪两指
    - my_gripper_fingers_to_stroke            # 两指求和 > 行程（米）
```
**相机图像**：
```
- target_key: "images.color.head"
  source_path: "streams.head"                 # 原始 Image 对象（store_raw_message）
  transforms: ros_image_to_jpeg               # 编码为 JPEG 字节
- target_key: "images.color.external"
  source_path: "streams.external"
  transforms: ros_image_to_jpeg
```
device_to_r2c映射项的字段：
表8字段说明 
| 字段                           | 说明                                                                        |
|:---|:---|
| target_key                   | R2C观测标准字段（joint_states.names/.position、images.color.xxx等）。              |
| source / source_path           | 取数来源。streams.\<store_as\>.字段或streams.\<store_as\>                         |
| source_index / target_index | 源/目标数组下标。                                                                 |
| default                      | 固定值（无source时用，如关节名表）。                                                    |
| transforms                     | 转换链（array_to_list、slice、ros_image_to_jpeg、my_gripper_fingers_to_stroke等）。 |
   
target_key与task一起构成最终Observations（云端策略所需字段详见R2C观测模型）。
**r2c_to_device：生成ROS2命令**
**机械臂**（前6维 \> arm发布器）：
```
- target: "commands_by_publisher.arm.data"    # 写入哪个发布器的哪个字段
  source: "joint_states.position"
  use_assign_dotted: true                     # 用点号路径赋值
  required: true
  transforms:
    - slice: { start: 0, end: 6 }             # 6 维机械臂
    - list_to_ros_float64_multi_array         # 组装 Float64MultiArray
```
**夹爪**（第7维行程 \> 展开两指 \> hand发布器）：
```
- target: "commands_by_publisher.hand.data"
  source: "joint_states.position"
  source_index: 6
  use_assign_dotted: true
  required: true
  transforms:
    - my_gripper_stroke_to_fingers            # 行程 > [S/2, S/2] 两指
    - list_to_ros_float64_multi_array
```
r2c_to_device映射项字段：
表9字段说明 
| 字段                 | 说明                                                                                 |
|:---|:---|
| target            | 命令写入目标，如commands_by_publisher.\<name\>.data。                                      |
| source              | 取自云端Action的字段，如joint_states.position。                                              |
| source_index       | 源下标（取第7维夹爪行程）。                                                                  |
| required          | 该字段是否必需。                                                                          |
| use_assign_dotted | 是否用点号路径完成嵌套赋值。                                                                    |
| transforms       | 反向转换链（slice, my_gripper_stroke_to_fingers, list_to_ros_float64_multi_array ...）。 |
   
最终翻译器输出commands_by_publisher（或commands_by_service/commands）给adapter.send_action()，由适配器按发布器名路由并发布到对应 Topic。
**自定义转换器** ：r2c_to_device里可引用自定义转换器（如SO101的so101_gripper_value_to_joint）。参考[Transformer使用指导](https://support.huaweicloud.com/sdkreference-cloudrobo/cloudrobo_03_0045.html)。
#### 五、常见ROS转换器速查
参考[Transformer使用指导](https://support.huaweicloud.com/sdkreference-cloudrobo/cloudrobo_03_0045.html)
来自core/transformers.py注册表，可在subscriptions.transforms / device_to_r2c.transforms / r2c_to_device.transforms中引用：
表10转换器作用说明 
| 转换器                                                         | 作用                                 |
|:---|:---|
| array_to_list                                                | numpy或数组转换到list                    |
| slice {start,end}                                              | 截取数组片段。                             |
| ros_message_to_mapping                                         | ROS消息转换到dict。                        |
| ros_image_to_jpeg/png/webp                                   | sensor_msgs/Image转换到编码字节。           |
| ros_compressed_image_to_jpeg/...                            | 压缩图像转换到编码字节。                       |
| select_joints_by_name {names}                               | 按关节名过滤出指定关节子集。                   |
| list_to_ros_float64_multi_array                              | list转换到std_msgs/Float64MultiArray。 |
| list_to_ros_joint_state {names}                              | list转换到sensor_msgs/JointState。     |
| list_to_ros_move_request                                     | 6维list转换到JAKA Move服务请求。            |
| scalar_to_ros_float64                                        | 标量转换到std_msgs/Float64。             |
| parallel_gripper_fingers_to_stroke / \*_stroke_to_fingers    | 两指位置与行程换算（0/对称）。                   |
| my_gripper_fingers_to_stroke / my_gripper_stroke_to_fingers | UR5e夹爪行程换算变体。                       |
   
#### 六. 调试与验证
1. 先配置dry_run验证链路，如下所示。 
   ```
   runtime:  
     dry_run: true   # 只发观测、不真正驱动仿真机器人
   ```
   确认日志显示：ROS2订阅有数据、device_to_r2c成功、观测已发布、云端能收到。无误后再改false。
   
   
2. 观测数据诊断。 
   查看ROS2侧话题是否有数据：ros2 topic echo /joint_states、ros2 topic hz /head_camera/camera_image_color。
   如果translator.device_to_r2c因缺字段跳过：日志会打印缺失的source_path，对照store_as 与 include_fields检查。
   
   
3. 执行诊断命令，确认command_publishers的话题名与仿真侧控制器话题完全一致（大小写、前导/）。 
   ```
   ros2 topic echo /arm_position_controller/commands
   ```
   观察是否收到Float64MultiArray。
   多台仿真机器人保证node_name不同、话题带命名空间。
   
   
4. 执行如下命令，联动Policy Server端到端。 
   ```
   # 终端 1：云端推理
   python -m r2c_sdk.inference.r2c_lerobot_policy_server \
     --client-config config/client_config.yaml \
     --cloud-config config/cloud_ur5e_act.yaml \
     --policy-type act --device cpu
   # 终端 2：仿真机器人边缘端
   python -m r2c_sdk.cloudroboclient \
     --client-config config/client_config.yaml \
     --robot-config r2c_ur5e.yaml
   ```
   
   
 
#### 七、常见问题处理
表11常见问题处理方法 
| 症状                                          | 可能原因                                       | 解决办法                                                            |
|:---|:---|:---|
| ROS2 adapter connected ... 但没有观测             | 话题名/消息类型不对，或DDS域不一致。                     | ros2 topic list；export ROS_DOMAIN_ID=x两端一致。                     |
| ModuleNotFoundError: No module named 'rclpy' | 未执行ROS2的setup.bash脚本。                    | source /opt/ros/${ROS_DISTRO}/setup.bash                       |
| No ROS2 command publisher found for 'arm'    | commands_by_publisher.\<name\> 与发布器名不一致。 | 对齐r2c_to_device.target 与 command_publishers键名                     |
| KeyError: 'joint_states.position'              | 订阅数据未就绪或字段被过滤。                            | 检查include_fields是否含position，订阅是否取到数。                              |
| 动作不执行                                      | dry_run: true /发布器话题不对。                  | 改false；核对话题名与切片/转换。                                             |
| 图像为空                                        | 未store_raw_message: true或编码转换缺失。         | 相机订阅加 store_raw_message: true，device_to_r2c 用 ros_image_to_jpeg |
| 多流时间不对齐                                     | 未开启观测同步。                                  | 启用observation_sync + 安装message_filters。                          |
   
