基于 ROS2 + 深度相机 + 激光雷达的控制接口设计与接入实践


当机器人平台进入业务落地阶段,真正决定系统可维护性的,往往不是“能不能跑起来”,而是“控制层能不能被标准化接管”。很多团队拿到一台现成的 ROS2 机器人后,第一反应是直接改原有 App 或者把业务逻辑硬塞进车端节点里。短期看能出效果,长期看却会把控制协议、业务流程和设备依赖耦合在一起,最终导致二次开发成本越来越高。

这篇文章聚焦一个更工程化的问题:如果你已经有一套自己的 Web 控制端,如何在不重写底盘控制逻辑不侵入业务系统的前提下,接管 MentorPi A1 这类 ROS2 机器人底盘。
真实接入效果:
在这里插入图片描述

这里只讨论控制层能力:

  • 底盘前进、后退、左转、右转、停止
  • 速度配置策略
  • 激光雷达数据接入与显示
  • 视频流获取与显示
  • 目标追踪接入与显示
  • WebSocket 通信
  • 心跳包与在线状态检测
  • 自研系统如何接入

不讨论建图、导航、SLAM、自动驾驶等上层算法话题。


一、为什么选择 MentorPi A1

MentorPi A1 的价值,不在于它“功能很多”,而在于它具备一个比较完整的机器人控制闭环:计算平台、底层控制器、相机、雷达、编码器、电机和 ROS2 软件栈是成套交付的。对于做二次开发的工程师来说,这意味着你不需要先花大量时间做设备拼装与驱动打通,而是可以直接把精力集中在接口接入、控制协议设计和业务联动上。

从公开资料看,MentorPi A1 是一套基于 Raspberry Pi 5 的 ROS2 机器人平台,官方资料给出的系统环境为 Ubuntu 22.04 LTS + ROS2 Humble,并配备 Ackermann 阿克曼底盘TOF 激光雷达3D 深度相机、高精度编码器电机,以及一套低层控制器与主控协同工作的双控制器架构。主控侧负责 ROS2、视觉、策略和网络通信,低层控制器负责电机、舵机、电池、电设等实时设备控制,这种分层对于业务系统接管非常重要,因为 Web 控制端实际上只需要面对主控层提供的 ROS2 接口,而不是直接操作底层硬件寄存器。

对于底盘形态,A1 采用的是阿克曼转向结构,而不是麦克纳姆轮。阿克曼的特点是运动学更接近真实车辆:可以稳定前进、后退和转向,但不能横向平移。这意味着 Web 控制端在设计方向键、摇杆和速度模型时,不能沿用全向底盘那套“x/y 双平面自由运动”的交互逻辑,而应当围绕“线速度 + 转向角速度”的组合来组织控制协议。

从接入角度看,MentorPi A1 还有三个优点。第一,ROS2 接口清晰,底盘控制、里程计、电池、雷达、图像流都有对应入口;第二,设备已经具备相机与雷达,便于业务系统同时获得运动和感知数据;第三,平台原生支持网络控制,适合通过 WebSocket + HTTP 视频流的方式做远程控制端。


二、系统控制架构设计

如果目标是“让 Web 控制端接管底盘”,最核心的设计原则只有一句话:业务系统不要直接碰硬件,统一通过 ROS2 控制层收发消息

在这个结构里,Web 控制端只做三件事:

  1. 通过 WebSocket 发布控制指令
  2. 通过 WebSocket 订阅状态数据
  3. 通过 HTTP 获取视频流

底盘控制节点只做两件事:

  1. 把标准速度指令转换成底盘可执行的电机/舵机动作
  2. 把里程计、电池、雷达等状态持续发布出来

用 Mermaid 表达,整个链路可以抽象为:

WebSocket

publish / subscribe / call_service

HTTP MJPEG / Snapshot

Web控制端

rosbridge_server

ROS2通信层

底盘控制节点

雷达控制节点

驱动节点

阿克曼底盘

激光雷达

电机 舵机 电池 IMU

web_video_server

相机图像Topic

[图片:系统控制架构图]

这个架构的好处是明确的:

  • Web 层和底盘硬件解耦
  • 控制协议可以被第三方系统复用
  • ROS2 节点可以持续演进,而不影响上层业务
  • 视频、雷达、底盘控制分离,便于按需替换

对工程团队而言,这种分层还意味着一个重要能力:以后换前端、不换底盘;换业务系统、不换控制协议;换交互方式、不换 ROS2 接口


三、ROS2控制链路解析

从本地源码看,底盘控制链路的核心非常清晰:Web 侧通过 rosbridge_server 进入 ROS2,总线中的关键接口集中在速度控制、状态反馈、雷达模式和图像流四类。

1. 关键 Topic

Topic Type 用途
/controller/cmd_vel geometry_msgs/msg/Twist 主底盘控制入口
/cmd_vel geometry_msgs/msg/Twist 兼容控制入口,适合作为急停补发零速通道
/odom nav_msgs/msg/Odometry 融合后的运动状态
/odom_raw nav_msgs/msg/Odometry 控制器原始里程计
/ros_robot_controller/battery std_msgs/msg/UInt16 电池电压原始值,单位毫伏
/scan_raw sensor_msgs/msg/LaserScan 激光雷达原始扫描数据
/ascamera/camera_publisher/rgb0/image sensor_msgs/msg/Image RGB 图像流
/ascamera/camera_publisher/depth0/image_raw sensor_msgs/msg/Image 深度图像流

其中最值得重点解释的是 /controller/cmd_vel/cmd_vel

从控制器节点实现来看,二者最终都会进入底盘执行逻辑,但角色不同:

  • /controller/cmd_vel:主控制入口,直接进入底盘控制回调
  • /cmd_vel:兼容入口,源码中带有限幅保护

这意味着在新系统接入时,应当把 /controller/cmd_vel 作为常规遥控链路,把 /cmd_vel 作为安全补发或兼容入口,而不是二者混用。

2. 关键 Service

Service Type 用途
/lidar_app/enter std_srvs/srv/Trigger 进入雷达控制模块
/lidar_app/exit std_srvs/srv/Trigger 退出雷达控制模块
/lidar_app/set_running interfaces/srv/SetInt64 切换雷达模式
/lidar_app/set_param interfaces/srv/SetFloat64List 设置雷达阈值、角度、速度
/lidar_app/heartbeat std_srvs/srv/SetBool 雷达模块心跳保活

3. 关键 Parameter

Parameter Type 默认值 用途
pub_odom_topic bool true 是否发布 /odom_raw
base_frame_id string base_footprint 底盘坐标系
odom_frame_id string odom 里程计坐标系
linear_correction_factor double 1.00 线速度修正
angular_correction_factor double 1.00 角速度修正
machine_type string 环境变量注入 区分底盘类型

这里要特别说明一点:源码里没有独立的“Web 速度配置服务”。也就是说,线速度、角速度的日常控制上限,当前更适合由 Web 控制端做本地限幅,而不是指望车端存在一个现成的速度参数接口。


四、遥控控制实现

遥控控制是所有二次开发里最基础、也是最容易被设计错的模块。错误通常不在“消息不会发”,而在“运动模型和底盘形态不匹配”。

MentorPi A1 采用阿克曼底盘。阿克曼底盘和麦轮最大的区别是:

  • 麦轮可以横移
  • 阿克曼不能横移
  • 麦轮更像平面机器人
  • 阿克曼更像小车

因此,Web 控制端不应暴露 linear.y 的业务语义,而应该围绕 linear.x + angular.z 组织控制:

  • 前进:linear.x > 0
  • 后退:linear.x < 0
  • 左转:angular.z > 0
  • 右转:angular.z < 0
  • 停止:全部置零

1. 遥控 JSON 示例

前进

{
  "op": "publish",
  "topic": "/controller/cmd_vel",
  "msg": {
    "linear": {
      "x": 0.12,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    }
  }
}

后退

{
  "op": "publish",
  "topic": "/controller/cmd_vel",
  "msg": {
    "linear": {
      "x": -0.12,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    }
  }
}

左转

{
  "op": "publish",
  "topic": "/controller/cmd_vel",
  "msg": {
    "linear": {
      "x": 0.10,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.30
    }
  }
}

右转

{
  "op": "publish",
  "topic": "/controller/cmd_vel",
  "msg": {
    "linear": {
      "x": 0.10,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": -0.30
    }
  }
}

停止

{
  "op": "publish",
  "topic": "/controller/cmd_vel",
  "msg": {
    "linear": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    }
  }
}

安全急停补发

{
  "op": "publish",
  "topic": "/cmd_vel",
  "msg": {
    "linear": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    },
    "angular": {
      "x": 0.0,
      "y": 0.0,
      "z": 0.0
    }
  }
}

2. ROS2 控制链路

Web控制端

controller/cmd_vel

底盘控制节点

阿克曼运动学

电机转速与转向舵机

底盘执行

3. 工程建议

  • 常规遥控统一走 /controller/cmd_vel
  • 停止或断线保护时,同时向 /controller/cmd_vel/cmd_vel 发送零速
  • 不要把阿克曼底盘当成全向底盘,不要暴露横移控制
  • 左右转最好带少量前进量,手感会比“原地摆头”更接近真实车辆

五、速度配置设计

很多团队在接入机器人底盘时,会把“速度配置”理解成一个必须由车端提供的参数接口。但从工程实践看,未必如此。

对于现有底盘源码,真正已经落在控制器里的速度相关逻辑主要有两类:

  1. /cmd_vel 路径内的安全限幅
  2. 控制器参数里的标定修正系数

这意味着“业务系统里给用户一个速度滑块”这件事,最合理的落点其实在 Web 控制端本身。也就是:

  • Web 端保存用户的速度设置
  • Web 端在发 Twist 前做限幅
  • ROS2 只负责执行已经成型的运动指令

1. 建议的速度配置表

配置项 单位 建议值 说明
最大线速度 m/s 0.05 ~ 0.20 手动遥控推荐范围
最大角速度 rad/s 0.10 ~ 0.50 手动转向推荐范围
加速度 m/s² 业务侧决定 当前源码未提供独立接口
减速度 m/s² 业务侧决定 当前源码未提供独立接口
急停线速度 m/s 0 强制停车
急停角速度 rad/s 0 强制停车

2. 建议的 Web 配置 JSON

{
  "module": "speed_config",
  "data": {
    "max_linear_speed": 0.20,
    "max_angular_speed": 0.50,
    "acceleration": null,
    "deceleration": null
  }
}

3. 与源码的映射关系

设计项 当前源码是否直接支持 说明
最大线速度配置接口 应由 Web 控制端本地限幅
最大角速度配置接口 应由 Web 控制端本地限幅
/cmd_vel 安全限幅 车端已做保护
线速度修正参数 属于标定参数,不是交互级速度配置
角速度修正参数 属于标定参数,不是交互级速度配置

4. 一个很实用的结论

如果你只是做“业务系统接管底盘”,先别着急改车端参数接口。把速度配置做成 Web 层策略,往往更快、更稳、更便于灰度调试。


六、激光雷达接入实践

激光雷达在这类系统里有两个价值:

  1. 给车端算法使用
  2. 给 Web 端做二维环境可视化

从本地源码看,雷达控制节点真正使用的是 sensor_msgs/msg/LaserScan,并订阅 /scan_raw。同时,源码虽然保留了 /scan 相关命名,但当前默认链路里滤波节点并未启用,所以业务上更应该以 /scan_raw 为准。

1. LaserScan 是什么

LaserScan 可以理解成“一圈角度上的距离数组”。它的核心字段包括:

  • angle_min:起始角
  • angle_max:结束角
  • angle_increment:每个采样点的角度步进
  • range_min:最小有效距离
  • range_max:最大有效距离
  • ranges:每个角度对应的距离值

典型消息示例:

{
  "op": "publish",
  "topic": "/scan_raw",
  "msg": {
    "angle_min": -3.14159,
    "angle_max": 3.14159,
    "angle_increment": 0.01745,
    "range_min": 0.05,
    "range_max": 12.0,
    "ranges": [0.62, 0.61, 0.60, 0.59]
  }
}

2. 雷达控制接口

雷达模块当前支持 enter -> set_running -> heartbeat 的工作流。

进入雷达模块

{
  "op": "call_service",
  "id": "srv_lidar_enter",
  "service": "/lidar_app/enter",
  "type": "std_srvs/srv/Trigger",
  "args": {}
}

设置模式

{
  "op": "call_service",
  "id": "srv_lidar_mode",
  "service": "/lidar_app/set_running",
  "type": "interfaces/srv/SetInt64",
  "args": {
    "data": 1
  }
}

设置参数

{
  "op": "call_service",
  "id": "srv_lidar_param",
  "service": "/lidar_app/set_param",
  "type": "interfaces/srv/SetFloat64List",
  "args": {
    "data": [0.60, 90.0, 0.20]
  }
}

参数数组的含义固定为:

序号 含义 单位 范围
1 阈值 threshold m 0.3 ~ 1.5
2 扫描角 scan_angle deg 0 ~ 90
3 行为速度 speed m/s > 0

3. 如何推送到 Web

有两种常见方式:

  • 直接把 LaserScan 通过 rosbridge 转给浏览器
  • 在中间层转换成前端更友好的二维点集

如果目标是快速接入,建议先走第一种。因为 LaserScan 本身信息足够完整,前端只需要做一次极坐标转笛卡尔坐标:

x = range * cos(angle)
y = range * sin(angle)

这样就能直接在 Canvas 或 WebGL 里画出二维雷达扇形图。


七、视频流接入实践

对 Web 控制端来说,视频永远是控制体验的关键组成部分。没有视频,远程操控几乎不可用;视频方案选错,带宽和延迟又会立刻失控。

常见的视频接入方案有四种:

  • MJPEG
  • RTSP
  • WebRTC
  • WebSocket 二进制流

从本地源码实际情况看,当前平台采用的是 web_video_server + HTTP MJPEG / Snapshot`,而不是 RTSP、WebRTC 或 WebSocket 视频流。

这套方案的优点很朴素:

  • 与 ROS 图像 Topic 对接简单
  • 浏览器接入成本低
  • 调试方便
  • 对控制台类应用足够实用

缺点也很明确:

  • 带宽占用相对更高
  • 长时间多路视频时不如 WebRTC 高效

1. 实际视频源

图像源 Topic 类型
RGB 图像 /ascamera/camera_publisher/rgb0/image sensor_msgs/msg/Image
深度图像 /ascamera/camera_publisher/depth0/image_raw sensor_msgs/msg/Image

2. 访问地址示例

RGB 实时流

http://<robot-ip>:8080/stream?topic=/ascamera/camera_publisher/rgb0/image

深度图实时流

http://<robot-ip>:8080/stream?topic=/ascamera/camera_publisher/depth0/image_raw

RGB 单帧快照

http://<robot-ip>:8080/snapshot?topic=/ascamera/camera_publisher/rgb0/image

3. 视频播放链路

相机节点发布Image

web_video_server

HTTP MJPEG流

Web控制端视频组件

[图片:视频流链路图]

4. 工程建议

  • 控制台优先用 MJPEG,简单稳定
  • 带宽不稳定时,回退为 snapshot 模式
  • 如果后续要做大规模多端观看,再评估 WebRTC
  • 视频链路最好独立于控制链路,避免 WebSocket 被图像数据拖垮

八、心跳包与在线状态检测

机器人系统和普通后台系统最大的不同之一,是它必须面对“网络断了,但底盘还在动”这个风险。

所以,心跳机制不是锦上添花,而是远程控制系统的基础安全能力。

从本地源码看,当前雷达模块采用的是Service 型心跳,而不是自定义 JSON ping/pong。也就是业务系统按固定周期调用:

  • /lidar_app/heartbeat

后端收到 data=true 后刷新过期时间;超时则自动退出模块。

1. 心跳请求示例

{
  "op": "call_service",
  "id": "srv_heartbeat_001",
  "service": "/lidar_app/heartbeat",
  "type": "std_srvs/srv/SetBool",
  "args": {
    "data": true
  }
}

2. 心跳响应示例

{
  "op": "service_response",
  "id": "srv_heartbeat_001",
  "service": "/lidar_app/heartbeat",
  "values": {
    "success": true,
    "message": "start"
  },
  "result": true
}

3. 推荐时序

雷达控制节点 rosbridge_server Web控制端 雷达控制节点 rosbridge_server Web控制端 loop [every 2s] 超过5秒未收到心跳 call_service /lidar_app/enter enter success success call_service /lidar_app/heartbeat {data:true} heartbeat success success 自动执行退出逻辑

4. 在线状态检测建议

真正可用的在线状态,不应该只看 WebSocket 是否连着,而应至少组合三类信息:

检测项 说明
WebSocket 连接状态 rosbridge 是否可达
/odom/odom_raw 更新时间 底盘状态是否持续刷新
/ros_robot_controller/battery 更新时间 驱动侧是否仍在线

一个很实用的经验是:把“连接在线”和“设备在线”分开显示。前者说明浏览器能连上 ROS;后者说明底盘和驱动还在持续工作。


九、目标追踪接入实践

在很多业务场景里,远程控制并不只是“按按钮让车走”,还需要让机器人围绕一个可见目标持续调整位置。对这种需求,更合理的做法不是把视觉算法搬到浏览器,而是让 Web 控制端接管追踪模块的启停、参数、保活和结果显示,算法本体继续运行在 ROS2 侧。

从本地源码看,目标追踪节点已经暴露出一套相对完整的控制接口,Web 侧并不需要理解图像识别细节,只需要按标准 Service 和图像流接入即可。

1. ROS2 接口清单

功能 ROS接口 类型 说明
进入追踪模块 /object_tracking/enter std_srvs/srv/Trigger 初始化追踪节点与图像订阅
退出追踪模块 /object_tracking/exit std_srvs/srv/Trigger 停止追踪并释放资源
开启/关闭追踪 /object_tracking/set_running std_srvs/srv/SetBool 控制追踪逻辑是否执行
设置追踪阈值 /object_tracking/set_threshold interfaces/srv/SetFloat64 设置追踪阈值
设置目标颜色 /object_tracking/set_target_color interfaces/srv/SetPoint 通过图像坐标取色
读取目标颜色 /object_tracking/get_target_color std_srvs/srv/Trigger 返回当前追踪颜色
模块保活 /object_tracking/heartbeat std_srvs/srv/SetBool 心跳保活
图像输入 /ascamera/camera_publisher/rgb0/image sensor_msgs/msg/Image RGB 输入流
结果图像 /object_tracking/image_result sensor_msgs/msg/Image 追踪结果预览
底盘控制输出 /controller/cmd_vel geometry_msgs/msg/Twist 追踪节点控制底盘

2. 目标追踪控制链路

Web控制端

object_tracking enter

object_tracking set_target_color

object_tracking set_threshold

object_tracking set_running

object_tracking heartbeat

RGB图像Topic

目标追踪节点

controller/cmd_vel

object_tracking/image_result

web_video_server

Web追踪画面

3. 接入流程

推荐的接入顺序如下:

  1. 调用 /object_tracking/enter
  2. 获取或显示 RGB 视频
  3. 在画面上选取目标颜色
  4. 调用 /object_tracking/set_target_color
  5. 调用 /object_tracking/set_threshold
  6. 调用 /object_tracking/set_running
  7. 周期发送 /object_tracking/heartbeat
  8. 显示 /object_tracking/image_result

4. 请求示例

进入追踪模块

{
  "op": "call_service",
  "id": "srv_track_enter",
  "service": "/object_tracking/enter",
  "type": "std_srvs/srv/Trigger",
  "args": {}
}

开启追踪

{
  "op": "call_service",
  "id": "srv_track_run",
  "service": "/object_tracking/set_running",
  "type": "std_srvs/srv/SetBool",
  "args": {
    "data": true
  }
}

设置追踪阈值

{
  "op": "call_service",
  "id": "srv_track_threshold",
  "service": "/object_tracking/set_threshold",
  "type": "interfaces/srv/SetFloat64",
  "args": {
    "data": 0.50
  }
}

设置目标颜色

这里不是直接传 RGB 数值,而是传图像中的归一化取色点

{
  "op": "call_service",
  "id": "srv_track_color",
  "service": "/object_tracking/set_target_color",
  "type": "interfaces/srv/SetPoint",
  "args": {
    "data": {
      "x": 0.42,
      "y": 0.36,
      "z": 0.0
    }
  }
}

读取当前目标颜色

{
  "op": "call_service",
  "id": "srv_track_get_color",
  "service": "/object_tracking/get_target_color",
  "type": "std_srvs/srv/Trigger",
  "args": {}
}

返回示例:

{
  "op": "service_response",
  "id": "srv_track_get_color",
  "service": "/object_tracking/get_target_color",
  "values": {
    "success": true,
    "message": "255,120,30"
  },
  "result": true
}

发送追踪心跳

{
  "op": "call_service",
  "id": "srv_track_heartbeat",
  "service": "/object_tracking/heartbeat",
  "type": "std_srvs/srv/SetBool",
  "args": {
    "data": true
  }
}

5. 参数说明

参数 类型 单位 建议范围 说明
threshold number - 0.1 ~ 0.8 追踪阈值,需现场调参
x number 归一化 0 ~ 1 相对图像宽度的采样点
y number 归一化 0 ~ 1 相对图像高度的采样点
心跳周期 number s 2 建议与雷达模块一致

6. 结果图像显示

目标追踪结果图像可以通过 web_video_server 直接显示:

http://<robot-ip>:8080/stream?topic=/object_tracking/image_result

7. 工程建议

  • 目标追踪应当被设计成独立模块,不要和手动遥控写进同一个状态机
  • 进入追踪前先确认视频可用,否则用户无法完成取色
  • set_target_color 传的是图像坐标,不是 RGB 文本
  • 关闭追踪时同步停止心跳,并补发底盘零速
  • 业务系统负责启停和显示,视觉算法仍应运行在 ROS2 侧

十、自研系统如何接管底盘

这是整篇文章最重要的部分。

如果你现在要做一个新的 Web 控制端,或者要把第三方业务系统接到这台机器人上,真正需要做的不是“理解全部源码”,而是先把控制面和显示面分离,再把接入清单列出来。

1. 最小可用接入清单

功能 ROS接口 Web接口
前进/后退/左转/右转/停止 /controller/cmd_vel WebSocket publish
急停保护 /controller/cmd_vel + /cmd_vel WebSocket publish
运动状态显示 /odom WebSocket subscribe
里程计兜底 /odom_raw WebSocket subscribe
电池状态显示 /ros_robot_controller/battery WebSocket subscribe
雷达数据显示 /scan_raw WebSocket subscribe
雷达模式控制 /lidar_app/set_running WebSocket call_service
雷达参数设置 /lidar_app/set_param WebSocket call_service
雷达保活 /lidar_app/heartbeat WebSocket call_service
目标追踪启停 /object_tracking/enter /object_tracking/set_running WebSocket call_service
目标追踪参数 /object_tracking/set_threshold /object_tracking/set_target_color WebSocket call_service
目标追踪保活 /object_tracking/heartbeat WebSocket call_service
目标追踪结果显示 /object_tracking/image_result HTTP /stream
RGB 视频显示 /ascamera/camera_publisher/rgb0/image HTTP /stream
深度视频显示 /ascamera/camera_publisher/depth0/image_raw HTTP /stream

2. 推荐接管步骤

第一步:先接控制,不接业务

先做一个最小控制台,只包含:

  • 连接状态
  • 视频画面
  • 四方向控制
  • 急停按钮
  • 电池和速度显示

这样可以先验证整个控制链路是否通。

第二步:再接状态

至少订阅:

  • /odom
  • /odom_raw
  • /ros_robot_controller/battery
  • /scan_raw

状态面板越早接上,后续联调成本越低。

第三步:最后接业务动作

例如巡检、远程值守、半自动流程等,都应该建立在控制面已经稳定的基础上,而不是一开始就把业务流程和底盘动作绑死。

3. 一个推荐的职责边界

层级 职责
Web控制端 指令输入、状态显示、速度策略、断线处理
ROS2控制层 标准 Topic/Service 暴露、运动执行、设备数据发布
业务系统 流程编排、业务状态机、任务逻辑
硬件层 电机、舵机、雷达、相机、电池

如果这个边界划不清,后面任何一个业务需求都会反复侵入底层。


十一、补充:扫描建图与导航实测问题

虽然本文重点讨论的是控制层接管,但在实际项目推进中,控制系统最终往往还是要和建图、定位、导航链路协同工作。基于现场测试,这个平台在“能跑通”和“跑得稳定”之间,仍然存在一些需要二次开发优化的地方。这里单独列出来,方便做系统评估时提前有预期。

1. 扫描建图的实测现象

在实际扫描过程中,如果机器人对同一区域进行两遍扫描,地图中容易出现较明显的重影和错位。直观表现是:

  • 墙体轮廓出现双边
  • 直线边界变厚
  • 局部障碍物位置出现轻微偏移
  • 同一结构在地图上叠出两层轮廓

这类问题通常意味着里程计、姿态估计和激光数据在回环不足或局部对齐不稳时存在累计误差。对业务层的影响是:地图“看起来能用”,但用于后续定位或路径规划时,误差会被进一步放大。

在这里插入图片描述

单遍扫描地图效果

在这里插入图片描述

双遍扫描地图重影效果

2. 导航旋转 90 度场景问题

现场测试中,一个非常典型的问题是:当机器人需要执行接近 90° 的原地转向或大角度姿态调整时,失败概率明显偏高,几乎可以视为高风险动作。

这类问题对阿克曼底盘尤其敏感,原因通常不只是“导航参数没调好”,还可能叠加以下因素:

  • 阿克曼底盘本身不适合严格原地旋转
  • 里程计角度估计误差被放大
  • 转向控制与速度控制耦合较强
  • 定位与局部控制器对姿态收敛要求过高

从工程视角看,这说明“导航动作模型”和“底盘实际运动模型”之间仍然存在不完全匹配。业务系统如果需要较强的姿态控制能力,不能默认平台出厂参数就能稳定支撑。

3. 实际距离误差

在实际测试中,约 4.8 m 的真实移动距离,测得存在约 0.6 m 的误差。这不是一个可以忽略的小偏差,因为它会直接影响:

  • 导航到点精度
  • 停靠位置精度
  • 地图与真实场景的一致性
  • 重复任务的稳定性

按这个量级估算,误差比例已经足以对较窄通道、门口停靠、工位接近等场景造成实际影响。

4. 官方反馈与工程含义

根据官方技术支持反馈,这类误差问题需要通过二次开发与参数优化来进一步减小,而不是单纯依赖默认配置。

这句话背后的工程含义很明确:

  • 平台具备二次开发基础
  • 但默认参数并不代表已经针对你的场景完成标定
  • 如果项目要求的是“演示可用”,默认配置可能够用
  • 如果项目要求的是“稳定交付”,就必须做控制与定位精度优化

5. 建议的优化方向

如果后续要继续推进建图与导航稳定性,建议从以下几个方向入手:

优化方向 说明
线速度与角速度重新标定 校正运动学误差,减小累计漂移
编码器与里程计修正 提高距离估计准确度
转向控制参数优化 降低阿克曼底盘大角度转向失败率
激光与底盘外参复核 避免传感器安装误差放大地图偏差
地图构建流程规范化 降低“重复扫两遍”带来的重影问题
场景分级验收 区分演示环境与业务环境,不混用评价标准

6. 对业务系统接入的启示

即使当前阶段的主要目标是“让 Web 控制端接管底盘”,也建议在系统设计里预留下面几项能力:

  • 地图效果对比展示区
  • 单遍/双遍地图样例展示
  • 定位误差记录区
  • 实测距离与理论距离对照表
  • 导航失败动作日志

这些内容平时看起来像“附加项”,但一旦项目进入联调或交付阶段,它们会直接决定你能否高效定位问题。


十二、最佳实践与避坑经验

1. 控制频率不要过低

遥控发布不是发一次就完事。建议持续发布速度指令,控制频率至少保持在 5~10Hz 以上;工程上 8~20Hz 更稳妥。

2. 不要把视频流塞进控制通道

WebSocket 负责控制和状态,视频单独走 HTTP。否则一旦网络波动,控制和视频会同时抖。

3. 雷达原始数据优先用 /scan_raw

当前源码主链路使用的是 /scan_raw。如果你默认订阅 /scan,很可能在现场发现根本没有数据。

4. 速度限制一定放在业务侧

即使车端有兼容限幅,Web 控制端也应自行限速。特别是在公网、弱网、浏览器多标签页等复杂环境下,本地限幅可以显著降低误操作风险。

5. 断线保护必须有“双零速”

不要只发一个 topic 的停止命令。更稳妥的方式是同时向:

  • /controller/cmd_vel
  • /cmd_vel

补发零速,确保不同控制链都能收到停车指令。

6. 在线判断不要只看连接灯

真正的在线判断至少应包含:

  • WebSocket 是否连接
  • /odom 是否刷新
  • 电池状态是否刷新

否则你会遇到一种很危险的假象:页面显示“已连接”,但车端其实已经卡死。

7. 视频带宽要预留冗余

MJPEG 的接入简单,但带宽占用不低。现场使用时,如果网络条件一般,建议:

  • 默认只开一路主视频
  • 深度图按需打开
  • 弱网环境优先 snapshot

8. 不要把底盘控制写成业务回调

控制层应该独立,业务逻辑应该订阅控制层,而不是反过来。把业务流程直接写进底盘控制节点,后续几乎一定会变成维护灾难。


十三、总结

MentorPi A1 这类平台真正适合做二次开发的原因,不是它“功能丰富”,而是它已经具备了比较标准的 ROS2 控制骨架:底盘运动、驱动状态、雷达数据、相机图像、网络接入都已经成型。对于工程团队来说,这样的平台最适合采用“控制层标准化,业务层独立演进”的方式接入。

从这次接管实践可以看到,Web 控制端要想真正稳定接管底盘,并不需要深入改写底层节点,而是要把以下几件事做好:

  • 用标准 Topic 接管运动控制
  • 用标准 Service 接管雷达模式
  • 用独立视频链路承载图像显示
  • 用心跳与状态订阅保障在线检测
  • 用速度策略和急停机制保证安全

更重要的是,控制层一旦被协议化,业务系统就不再与具体前端形态绑定。今天可以是 Web 控制端,明天也可以是大屏系统、调度平台、远程值守终端,甚至其他第三方系统。只要接口不变,系统就能持续演进。

这也是机器人软件工程里一个很值得坚持的原则:业务会变,交互会变,算法会变,但控制协议最好尽量稳定。

如果你准备让自己的系统接管一台 ROS2 机器人,先把控制层理顺,往往比一开始追求复杂功能更重要。


参考资料

  1. Hiwonder 官方 MentorPi A1 产品页:https://www.hiwonder.com/products/mentorpi-a1
  2. Hiwonder 官方 MentorPi A1 单目版产品页:https://www.hiwonder.com/products/mentorpi-a1-monocular-camera-version
  3. Hiwonder 官方 MentorPi 文档总入口:https://docs.hiwonder.com/projects/MentorPi/en/latest/
  4. Hiwonder 官方 MentorPi Getting Ready:https://docs.hiwonder.com/projects/MentorPi/en/latest/docs/1.getting_ready.html
  5. Hiwonder 官方 MentorPi Lidar Lesson:https://docs.hiwonder.com/projects/MentorPi/en/2024-versions/docs/5.lidar_lesson.html
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐