返回教程正文

配套源码

README.md

app/bricks/ros_gateway/README.md
ros_gateway Brick:在 App Lab 中建立可靠的 WebSocket 通道app/bricks/ros_gateway/README.md
Markdown180 行
  1. # ROS Gateway Custom Brick
  2. `ros_gateway` 是一个运行在 Arduino App Lab 主容器中的 Python Custom Brick。它提供
  3. WebSocket 服务、JSON 报文校验、单客户端控制权、心跳与运动命令看门狗,让 App 可以和
  4. 容器外的客户端交换控制命令与状态。
  5. 这个 Brick **不是 ROS 2 节点**:它不导入 `rclpy`,也不创建 ROS 2 话题、服务、参数或
  6. 动作。项目中的原生 `ventuno_app_bridge` 包才负责把 ROS 2 图中的接口转换为本 Brick 的
  7. WebSocket 报文。
  8. 本目录是 `ros-gateway-loopback` 使用的基础协议版本,只包含底盘速度、模式和通用状态。
  9. 单电机报文是综合 App 中另一份扩展实现,不能直接假定本目录已提供。
  10. ## 组件边界
  11. ```text
  12. ROS 2 topics / services / parameters / actions
  13. ↓ 由独立原生 ROS 2 节点负责映射
  14. WebSocket 客户端
  15. ⇅ ws://127.0.0.1:8765/ros
  16. ros_gateway Brick(App Lab 容器)
  17. ⇅ Python 回调与发布方法
  18. App 的 python/main.py
  19. ```
  20. | 层 | 本组件是否负责 |
  21. | --- | --- |
  22. | WebSocket 监听、连接和 JSON 协议 | 是 |
  23. | 字段、范围、序号、时间戳校验 | 是 |
  24. | 断线和命令超时回调 | 是 |
  25. | ROS 2 话题、服务、参数、动作 | 否,由原生桥接节点负责 |
  26. | CAN、电机、IMU 等硬件访问 | 否,由其他 Brick 或 App 回调负责 |
  27. ## 目录
  28. ```text
  29. bricks/ros_gateway/
  30. ├── __init__.py
  31. ├── gateway.py
  32. ├── protocol.py
  33. ├── brick_config.yaml
  34. ├── requirements.txt
  35. └── README.md
  36. ```
  37. ## 在 App 中引用
  38. `app.yaml` 引用目录名与 `brick_config.yaml` 中 `id` 同为 `ros_gateway`:
  39. ```yaml
  40. bricks:
  41. - ros_gateway:
  42. variables:
  43. ROS_GATEWAY_HOST: "0.0.0.0"
  44. ROS_GATEWAY_PORT: "8765"
  45. ROS_GATEWAY_PATH: "/ros"
  46. ROS_GATEWAY_MAX_VX: "0.8"
  47. ROS_GATEWAY_MAX_VY: "0.8"
  48. ROS_GATEWAY_MAX_WZ: "1.5"
  49. ROS_GATEWAY_COMMAND_TIMEOUT_MS: "300"
  50. ROS_GATEWAY_HEARTBEAT_TIMEOUT_MS: "3000"
  51. ```
  52. `@brick` 会把实例纳入 App Lab 生命周期。创建对象、注册回调后调用
  53. `App.run(user_loop=...)`;App 启动时调用 Brick 的 `start()`,停止时调用 `stop()`。
  54. 业务循环不能阻塞 WebSocket 后台线程。
  55. ```python
  56. from arduino.app_utils import App
  57. from ros_gateway import RosGateway
  58. gateway = RosGateway()
  59. def handle_cmd_vel(command):
  60. print(command)
  61. def handle_safe_stop(reason):
  62. # 正式机器人必须在这里执行真实硬件停车。
  63. print("SAFE_STOP", reason)
  64. gateway.on_cmd_vel(handle_cmd_vel)
  65. gateway.on_stop(handle_safe_stop)
  66. App.run(user_loop=lambda: None)
  67. ```
  68. ## Python API
  69. | API | 语义 |
  70. | --- | --- |
  71. | `on_cmd_vel(callback)` | 注册已通过协议校验的 `vx/vy/wz` 命令回调 |
  72. | `on_mode_change(callback)` | 注册模式切换回调;显式返回 `False` 表示拒绝 |
  73. | `on_stop(callback)` | 注册断线、心跳超时、运动命令超时及 App 停止时的统一安全回调 |
  74. | `publish_base_state(state)` | 校验并排队发送底盘状态 |
  75. | `publish_imu(imu)` | 校验并排队发送 IMU 状态;当前协议未冻结坐标系和单位 |
  76. | `publish_diagnostics(data)` | 排队发送可 JSON 序列化的诊断字典 |
  77. | `is_ros_connected()` | 是否存在完成 `role=ros2` 握手的 WebSocket 客户端 |
  78. | `get_status()` | 返回连接、模式、队列、丢包数和服务错误快照 |
  79. `is_ros_connected()` 的名字来自项目约定。它只说明指定角色的 WebSocket 会话已建立,
  80. 不代表 ROS 2 图发现正常,也不检查某个话题、服务或节点是否存在。
  81. 发布方法返回 `True` 表示消息进入出站队列;没有活动客户端时返回 `False`。队列已满时
  82. 会丢弃最旧消息并增加 `dropped_messages`,不会无限积压状态。
  83. ## 默认配置
  84. | 环境变量 | 默认值 | 含义 |
  85. | --- | --- | --- |
  86. | `ROS_GATEWAY_HOST` | `0.0.0.0` | App 容器内监听地址 |
  87. | `ROS_GATEWAY_PORT` | `8765` | WebSocket 端口 |
  88. | `ROS_GATEWAY_PATH` | `/ros` | 请求路径 |
  89. | `ROS_GATEWAY_MAX_VX` | `0.8` | `vx` 绝对值上限,m/s |
  90. | `ROS_GATEWAY_MAX_VY` | `0.8` | `vy` 绝对值上限,m/s |
  91. | `ROS_GATEWAY_MAX_WZ` | `1.5` | `wz` 绝对值上限,rad/s |
  92. | `ROS_GATEWAY_COMMAND_TIMEOUT_MS` | `300` | `cmd_vel` 时效和执行看门狗 |
  93. | `ROS_GATEWAY_HEARTBEAT_TIMEOUT_MS` | `3000` | 有效客户端消息接收超时 |
  94. 端口由 `brick_config.yaml` 声明并映射到宿主。当前默认宿主端点为:
  95. ```text
  96. ws://127.0.0.1:8765/ros
  97. ```
  98. ## WebSocket 协议
  99. 协议版本为 `1`,每个 WebSocket 消息都是 UTF-8 JSON 对象。最大消息为 16 KiB,压缩
  100. 关闭,同时只允许一个活动控制客户端。
  101. 第一条消息必须在 2 秒内完成握手:
  102. ```json
  103. {"version":1,"type":"hello","role":"ros2","node":"ventuno_app_bridge_node"}
  104. ```
  105. 除 `hello` 外,客户端报文都必须包含严格递增的非负整数 `seq` 和 Unix 毫秒
  106. `timestamp_ms`。客户端时间不能比服务端超前超过 2000 ms;`cmd_vel` 还必须在默认
  107. 300 ms 时效内。
  108. | `type` | 方向 | 用途 |
  109. | --- | --- | --- |
  110. | `hello` | WebSocket 双向 | 建立应用层会话 |
  111. | `heartbeat` | WebSocket 双向 | 检测通信失联 |
  112. | `mode_change` | 客户端 → Brick | 请求 `IDLE`、`ROS_TELEOP` 或 `ESTOP` |
  113. | `cmd_vel` | 客户端 → Brick | 提交 `vx`、`vy`、`wz` |
  114. | `ack`、`error` | Brick → 客户端 | 返回命令结果或结构化错误 |
  115. | `base_state`、`imu`、`diagnostics` | Brick → 客户端 | 发送 App 状态 |
  116. 非零 `cmd_vel` 只允许在 `ROS_TELEOP` 模式下执行。合法速度超过
  117. `ROS_GATEWAY_COMMAND_TIMEOUT_MS` 未刷新时会调用一次 `on_stop("cmd_vel_timeout")`;
  118. WebSocket 断线、心跳超时或 App 停止也会调用 `on_stop`。Brick 只能触发回调,真正停车
  119. 必须由 App 在回调中实现。
  120. ## 与 ROS 2 的关系
  121. 当前 `ventuno_app_bridge` 示例节点提供以下 **ROS 2 公共 API**:
  122. | ROS 2 类别 | 名称 | 类型 |
  123. | --- | --- | --- |
  124. | 话题订阅 | `/cmd_vel` | `geometry_msgs/msg/Twist` 或 `TwistStamped` |
  125. | 话题发布 | `/ventuno/connection` | `std_msgs/msg/Bool` |
  126. | 话题发布 | `/ventuno/base_state` | `std_msgs/msg/String` |
  127. | 参数 | `websocket_url` 等 5 项 | 节点参数 |
  128. | 服务 | 无业务服务 | 当前示例未定义 |
  129. | 动作 | 无 | 当前示例未定义 |
  130. 上述接口属于独立 ROS 2 包,不属于 Brick。ROS 2 中 `.msg`、`.srv`、`.action` 是接口定义;
  131. 参数是节点持有的配置,并通过 ROS 2 参数服务和事件机制访问。
  132. ## 版本兼容
  133. 基础回环 App 与综合电机 App 中的 `ros_gateway` 源码不是同一版本:综合 App 增加了
  134. `motor_enable`、`motor_set_speed`、`motor_stop`、`motor_state` 和相应 Python API。
  135. 复制 Brick 时应复制完整目录并以本目录 README 为准,不能只复制同名文件后混用两版
  136. `gateway.py` 与 `protocol.py`。