如果你最近刷到过“天线宝宝机器人上门保洁”的视频,大概率会看到同一条弹幕:纯·人工·智能。视频里所谓的“机器人”,其实是工作人员穿着天线宝宝玩偶服拖地擦窗,收费 200 元/小时。网友调侃这波操作是“人工 + 智能 = 人工智能”,也有人把它叫作“具身智能的地板形态”。
这个梗本身很好笑,但它也引出一个值得技术人思考的问题:真正意义上的服务机器人上门保洁,到底需要哪些技术?作为开发者,如果我们要做一个能自己找到房间、规划路线、避开障碍物、然后把地拖干净的机器人,应该从哪里开始?
本文不讨论玩偶服,而是围绕“服务型移动机器人如何完成一次上门保洁任务”展开。我会从服务机器人的核心技术栈讲起,重点拆解 SLAM 建图、自主导航、路径规划、清扫执行这几个环节,并给出一个基于 ROS2 + Nav2 的最小可运行演示。无论你是在做课设、准备机器人相关竞赛,还是想给公司做一套室内清洁机器人原型,这篇文章都能帮你建立清晰的工程落地思路。
1. 背景与核心概念:从“人工”到“人工智能”差了多远
1.1 “上门保洁”这件事,为什么对机器人来说很难
很多人以为扫地机器人已经普及,做一台“会上门拖地”的机器人应该不难。但扫地机器人和“上门保洁机器人”是两个完全不同的物种。
家用扫地机器人面对的是相对固定的家庭环境,工作区域小,速度慢,就算漏扫了用户也不会太介意。而“上门保洁机器人”要面对的往往是完全陌生的房间:
- 户型未知,没有提前建好的地图。
- 家具摆放随意,地面可能有数据线、玩具、宠物。
- 光线变化大,窗户附近和床底亮度可能差出好几倍。
- 用户对“干净”有主观要求,不是走过一遍就算完成。
所以,一台能上门保洁的机器人,至少要解决四个问题:我在哪里?我要去哪?怎么安全地过去?到了之后怎么把活干好?
这四个问题对应到技术栈上,就是定位、建图、导航、操作执行。这也是服务机器人的核心闭环。
1.2 服务机器人关键技术栈
先给不熟悉机器人开发的同学一个整体视图。一台室内服务机器人通常由以下几层组成:
| 层次 | 核心模块 | 常用技术方案 |
|---|---|---|
| 感知层 | 激光雷达、深度相机、IMU、轮式里程计、碰撞传感器 | 2D/3D LiDAR、RGB-D 相机 |
| 认知层 | SLAM 建图、全局定位、语义识别 | Cartographer、slam_toolbox、AMCL |
| 规划层 | 全局路径规划、局部避障 | Nav2、TEB、DWA |
| 控制层 | 底盘运动控制、清洁机构控制 | ROS2 Control、PID、状态机 |
| 交互层 | App/语音/远程接管 | 语音识别、WebRTC、MQTT |
这是一套非常典型的“感知-决策-执行”架构。本文重点放在认知层和规划层,因为这两层决定了机器人能不能“找得到路”,而清扫执行更多是机械结构和控制策略的问题。
1.3 “纯人工”的梗,背后是“人机协同”的真实需求
天线宝宝保洁视频里的“纯人工”,其实是人完全接管了所有决策。而在真实产品中,人机协同并不是贬义词。
目前绝大多数商业服务机器人并不是全自主的。比如酒店送餐机器人遇到复杂餐桌位置、电梯联动异常时,后台会有安全员远程接管;室外巡检机器人在极端天气下也会切换到人工遥控。真正稳定可靠的全自主系统,目前只存在于限定场景中。
所以正确的心态是:先把“人能远程接管、机器人能自主跑简单路线”做扎实,再逐步提升自主比例。这也是本文演示案例的设计思路——让机器人自主完成“从充电桩到目标房间,再按弓字形路径清扫,最后返回充电桩”,同时保留人工急停和远程取消任务的接口。
2. 环境准备与版本说明
在开始写代码之前,先确认环境。下面的版本组合是我推荐给初学者的搭配,稳定、资料多、坑相对少。
| 软件 | 推荐版本 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 22.04 LTS | 机器人开发最主流的 Linux 发行版 |
| ROS 2 | Humble Hawksbill | 对应 Ubuntu 22.04,长期支持版 |
| 仿真器 | Gazebo | 配合 ROS2 使用,做传感器仿真 |
| 建图算法 | Cartographer 或 slam_toolbox | 2D 激光雷达建图 |
| 导航框架 | Nav2 | ROS2 官方导航栈 |
| 编程语言 | Python 3.10 + C++17 | Python 负责业务逻辑,C++ 负责性能敏感模块 |
| 机器人模型 | TurtleBot3 或自定义 URDF | 本文演示以差速轮底盘为例 |
版本说明:以上版本是截至当前比较通用的组合。不同发行版的 ROS2 命令和包名会有差异,如果你使用的是 ROS2 Foxy 或 ROS2 Jazzy,请以官方文档为准。本文示例代码重点讲思路,不要直接照搬到生产环境。
另外,如果电脑性能一般,建议至少 8GB 内存、4 核 CPU,并预留 20GB 磁盘空间给 Gazebo 模型库和 ROS2 依赖。GPU 不是必须的,2D 激光雷达导航用 CPU 就能跑得动。
2.1 安装 ROS2 基础环境
安装 ROS2 时不要用一键脚本盲目安装。官方推荐的安装方式是从 apt 源安装,以下是核心步骤,具体版本号请以 ROS2 官方文档为准:
# 设置编码 sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 # 添加 ROS2 apt 源(以 Ubuntu 22.04 + Humble 为例) sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update && sudo apt install curl curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg # 安装 ROS2 基础版 sudo apt install ros-humble-desktop安装完成后,在.bashrc中追加:
source /opt/ros/humble/setup.bash再安装后续需要用的依赖包:
sudo apt install ros-humble-nav2-bringup ros-humble-slam-toolbox ros-humble-turtlebot3*这里提醒一句:turtlebot3*是一组模拟器驱动包,方便我们有现成机器人模型可用。如果你用的是自研底盘,不需要装这块,但需要自己写 URDF 和驱动。
2.2 验证环境
新开一个终端,启动一个最简单的 ROS2 节点测试:
ros2 run demo_nodes_cpp talker另一个终端:
ros2 topic list能看到/chatter话题,就说明 ROS2 环境正常。如果看不到,优先检查source /opt/ros/humble/setup.bash是否写入.bashrc。
3. 核心原理拆解:机器人怎么知道自己在哪里、往哪走
3.1 SLAM:一边建地图,一边定位
“上门保洁”的第一步,是让机器人认识房间。在陌生环境中,机器人需要通过传感器采集环境信息,同时构建一张可供导航用的地图,这个过程叫做 SLAM(Simultaneous Localization and Mapping,同时定位与建图)。
SLAM 可以简单理解为:机器人一边走,一边问自己两个问题——“我在哪?”和“周围长什么样?”。它把激光雷达或深度相机采集到的点云数据,转换成一张二维栅格地图,地图中每个格子要么是空闲,要么是障碍物,要么是未知区域。
2D 激光雷达是室内服务机器人的首选传感器,因为便宜、稳定、计算量小。激光雷达扫描得到的是一个平面上的点集,SLAM 算法把这些点投影到二维坐标系,就能得到类似“俯视图”的环境轮廓。
SLAM 的产物是一张.pgm地图图片和对应的.yaml配置文件。地图建成之后,机器人在实际运行中不再需要实时建图,而是使用 AMCL(Adaptive Monte Carlo Localization,自适应蒙特卡洛定位)这类算法,在地图中做粒子滤波定位。
对于初学者,不需要从零实现 SLAM 算法。我们在 ROS2 中一般直接调用slam_toolbox或cartographer。slam_toolbox 更适合中小型室内场景,计算量小;Cartographer 在复杂环境、回环检测上更优,但配置复杂。
3.2 Nav2:从“地图”到“能走”的导航栈
有了地图和定位,接下来就是导航。ROS2 中负责导航的框架叫 Nav2,它是 ROS1 时代 move_base 的继任者,内部包含:
- 全局代价地图(global costmap):用于规划从起点到目标点的全局路径。
- 局部代价地图(local costmap):用于实时避障和局部路径调整。
- 全局规划器:常见的是 NavFn,基于 A* 或 Dijkstra 算法搜索路径。
- 局部规划器:常见的是 DWA 和 TEB,负责输出速度指令。
- 行为树:用于管理导航任务,例如“先旋转方向,再前进,遇到障碍就恢复”。
Nav2 的输入是一个目标位姿(目标点的 x、y 坐标和朝向),输出是底盘的速度指令。它对外提供 action 接口,叫NavigateToPose。我们只需要调用这个 action,就能让机器人从当前位置走到目标点。
需要特别注意:导航不是“给定目标点就走直线”。全局路径会绕开纸面上的大障碍物,局部路径会根据传感器实时避开新增障碍物。如果路径完全被堵死,Nav2 会返回失败,同时机器人会原地尝试恢复。
3.3 代价地图:机器人的“安全距离”意识
大多数第一次接触导航的人会忽略代价地图中的膨胀半径(inflation radius)。简单说,即便激光雷达测到的障碍物是一个点,机器人也不能真的贴着它走。因为机器人本身有体积,而且导航误差和底盘控制误差会让它偏离规划路径。
代价地图会给障碍物周围的栅格增加一个“代价”,越靠近障碍物代价越高。全局规划器在搜索路径时,会优先选择累计代价低的路径。
在实际配置时,要把膨胀半径设置为“机器人半径 + 安全余量”。如果设得太大,机器人会无法通过窄门;设得太小,机器人容易蹭到墙。这个参数需要结合现场环境反复调。
3.4 清扫执行:从“走到了”到“干完了”
导航只负责把机器人送到目标点。真正的保洁工作,需要机器人到了目标区域之后,按照一定路径遍历整个区域,把地面覆盖干净。
最常见的清扫路径是“弓字形”(Boustrophedon path),也就是像耕田一样来回扫描。这种路径覆盖率高,实现简单,适合矩形房间。实现弓字形清扫通常有两种方式:
- 纯业务层实现:机器人到了房间起点后,关闭 Nav2,按照固定速度指令来回移动,直到覆盖整个区域。
- 算法层实现:根据房间边界和障碍物,离线生成一系列目标点,再用 Nav2 逐个导航过去。
方式一实现简单,适合演示;方式二覆盖更精确,但需要额外编写区域划分算法。本文演示中采用方式一,用状态机控制机器人来回移动。
需要注意的是,真实保洁机器人还要考虑清洁机构(拖布、滚刷)的升降控制。到了目标区域放下抹布,转弯时抬起抹布,避免污染已经拖过的区域。这个控制逻辑可以放在机器人底盘驱动之上,通过状态机统一调度。
4. 完整实战:做一个“导航+清扫”的最小演示
这一节我们动手搭建一个精简版的上门保洁机器人演示。场景假设如下:
- 室内环境已建好地图,地图文件为
room_map.pgm和room_map.yaml。 - 机器人从充电桩出发。
- 目标房间的入口点坐标已知。
- 机器人导航到房间入口后,执行弓字形清扫动作。
- 清扫完成后,机器人返回充电桩。
我们使用 TurtleBot3 仿真模型 + Nav2 导航栈。之所以用模拟器,是因为真实机器人底盘、激光雷达、安全传感器都需要硬件联调,初学者在仿真环境里先跑通逻辑,成本更低,也更容易复现问题。
4.1 创建 ROS2 工作空间
mkdir -p ~/clean_robot_ws/src cd ~/clean_robot_ws/src ros2 pkg create clean_robot --build-type ament_python --dependencies rclpy geometry_msgs nav2_msgs action_msgs上面这条命令会创建一个名为clean_robot的 Python 包,并自动添加rclpy、geometry_msgs、nav2_msgs、action_msgs依赖。如果你的环境里没有自动创建依赖,也可以稍后在package.xml中手动补充。
创建完成后,目录结构如下:
clean_robot_ws/ └── src/ └── clean_robot/ ├── package.xml ├── setup.py ├── setup.cfg └── clean_robot/ └── __init__.py4.2 编写导航 Python 节点
在clean_robot/clean_robot/目录下新建navigation_client.py。这个节点的职责是:等待 Nav2 的 action server 就绪,发送目标点,等待机器人到达。
# 文件:clean_robot_ws/src/clean_robot/clean_robot/navigation_client.py import rclpy from rclpy.node import Node from rclpy.action import ActionClient from geometry_msgs.msg import PoseStamped from nav2_msgs.action import NavigateToPose from action_msgs.msg import GoalStatus class NavigationClient(Node): def __init__(self): super().__init__('navigation_client') self._client = ActionClient(self, NavigateToPose, 'navigate_to_pose') self._goal_handle = None def send_goal(self, x, y, yaw=0.0): """发送一个导航目标点""" goal_msg = NavigateToPose.Goal() # 构造目标位姿 pose = PoseStamped() pose.header.frame_id = 'map' pose.header.stamp = self.get_clock().now().to_msg() pose.pose.position.x = float(x) pose.pose.position.y = float(y) # 只用朝向角 yaw,四元数其他分量置 0 pose.pose.orientation.z = float(yaw) pose.pose.orientation.w = 1.0 goal_msg.pose = pose self.get_logger().info(f'发送导航目标: ({x}, {y})') # 等待 Nav2 action server 就绪 self._client.wait_for_server() self._send_goal_future = self._client.send_goal_async( goal_msg, feedback_callback=self.feedback_callback ) self._send_goal_future.add_done_callback(self.goal_response_callback) def goal_response_callback(self, future): goal_handle = future.result() if not goal_handle.accepted: self.get_logger().error('目标被 Nav2 拒绝') return self.get_logger().info('目标已被 Nav2 接受') self._goal_handle = goal_handle self._get_result_future = goal_handle.get_result_async() self._get_result_future.add_done_callback(self.get_result_callback) def feedback_callback(self, feedback_msg): feedback = feedback_msg.feedback self.get_logger().info(f'当前距离目标还有: {feedback.distance_remaining:.2f} 米') def get_result_callback(self, future): result = future.result() if result.status == GoalStatus.STATUS_SUCCEEDED: self.get_logger().info('导航成功') else: self.get_logger().warning(f'导航结束,状态码: {result.status}') rclpy.shutdown()代码解释:
ActionClient(self, NavigateToPose, 'navigate_to_pose'):创建 Nav2 的 action 客户端。客户端和服务端之间通过 action 通信,action 比 service 更适合长耗时任务,因为它支持进度反馈。PoseStamped:ROS2 中表示“带时间戳的位姿”的消息类型。这里把frame_id设为map,表示坐标是在地图坐标系下的。feedback_callback:Nav2 会周期性返回机器人离目标点的剩余距离,我们可以用这个信息做进度展示。GoalStatus.STATUS_SUCCEEDED:表示导航状态成功。注意,导航成功只代表“机器人走到了目标点”,不代表“保洁完成”。
4.3 编写清扫执行节点
清扫执行节点使用一个简单的状态机:导航到房间入口 → 弓字形移动 → 返回导航客户端 → 回到充电桩。
# 文件:clean_robot_ws/src/clean_robot/clean_robot/clean_controller.py import rclpy import time from rclpy.node import Node from geometry_msgs.msg import Twist class CleanController(Node): def __init__(self): super().__init__('clean_controller') self.cmd_pub = self.create_publisher(Twist, '/cmd_vel', 10) self.clean_state_pub = self.create_publisher( String, '/clean_state', 10 ) def set_clean_state(self, state): """发布清洁状态,方便可视化监控""" msg = String() msg.data = state self.clean_state_pub.publish(msg) def clean_rectangle(self, width=2.0, length=3.0, speed=0.2): """模拟弓字形清扫:在一个矩形区域内来回移动""" self.set_clean_state('cleaning_start') self.get_logger().info('开始弓字形清扫') # 每条直线的行驶时间 forward_time = length / speed turn_time = 1.5 # 固定转向时间,演示用 # 来回覆盖 5 趟 for i in range(5): # 向前 self.publish_cmd(0.2, 0.0) time.sleep(forward_time) self.publish_cmd(0.0, 0.0) time.sleep(0.2) # 转向,换行 if i % 2 == 0: self.publish_cmd(0.0, 0.4) else: self.publish_cmd(0.0, -0.4) time.sleep(turn_time) self.publish_cmd(0.0, 0.0) time.sleep(0.2) self.set_clean_state('cleaning_done') self.get_logger().info('弓字形清扫完成') def publish_cmd(self, linear_x, angular_z): twist = Twist() twist.linear.x = linear_x twist.angular.z = angular_z self.cmd_pub.publish(twist) def stop(self): self.publish_cmd(0.0, 0.0) self.get_logger().info('机器人已停止')这里补充说明:上面的代码在真实机器人上直接使用time.sleep是不可靠的,因为真实环境存在打滑、轮速误差、惯性等因素。更好的做法是结合轮式里程计(odometry)或激光雷达数据做闭环控制。但在仿真演示中,用 open-loop 控制可以帮助我们快速验证整体流程。
4.4 编写主流程:先导航,再清扫,最后返回
新建clean_task.py,把导航和清扫串起来。
# 文件:clean_robot_ws/src/clean_robot/clean_robot/clean_task.py import rclpy from rclpy.node import Node from navigation_client import NavigationClient from clean_controller import CleanController def main(): rclpy.init() nav_client = NavigationClient() clean_controller = CleanController() # 假设机器人从充电桩出发 # 房间入口点坐标,根据实际地图修改 room_entrance_x = 2.0 room_entrance_y = 1.5 # 第一步:导航到房间入口 nav_client.send_goal(room_entrance_x, room_entrance_y, yaw=1.57) # 等待导航完成 rclpy.spin(nav_client) # 第二步:执行清扫 clean_controller.clean_rectangle() # 第三步:返回充电桩 nav_client = NavigationClient() nav_client.send_goal(0.0, 0.0, yaw=0.0) rclpy.spin(nav_client) clean_controller.stop() rclpy.shutdown() if __name__ == '__main__': main()需要说明的是,ROS2 中不推荐连续创建两个节点实例来完成一个任务,因为rclpy.spin()会阻塞当前线程。上面的写法只是为了演示代码逻辑清晰。在工程实现中,更推荐使用单一节点,内部用状态机维护“导航中/清扫中/返航中”等状态。
4.5 配置 setup.py
为了让ros2 run能找到我们自己写的节点,需要在setup.py中注册入口点。
# 文件:clean_robot_ws/src/clean_robot/setup.py entry_points={ 'console_scripts': [ 'navigation_client = clean_robot.navigation_client:main', 'clean_controller = clean_robot.clean_controller:main', 'clean_task = clean_robot.clean_task:main', ], },然后在工作空间根目录编译:
cd ~/clean_robot_ws colcon build source install/setup.bash4.6 启动仿真、地图和 Nav2
如果你的环境里使用的是 TurtleBot3 仿真,需要先安装模型和环境变量。
export TURTLEBOT3_MODEL=burger export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/opt/ros/humble/share/turtlebot3_gazebo/models启动 Gazebo 仿真环境:
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动导航之前,需要先有一张地图。如果你没有现成地图,可以先启动 Cartographer 或 slam_toolbox 手动遥控机器人建图。建图操作步骤如下:
- 启动建图节点。
- 打开 rviz2,查看 LaserScan 和 Map 话题。
- 用键盘控制节点或手柄遥控机器人走遍房间的每个边界。
- 地图完整度足够后,保存为
room_map.pgm和room_map.yaml。
保存地图可以使用 map_saver:
ros2 run nav2_map_server map_saver_cli -f ~/clean_robot_ws/maps/room_map然后启动 Nav2,指定地图文件:
ros2 launch nav2_bringup bringup_launch.py map:=/home/<your_name>/clean_robot_ws/maps/room_map.yaml如果你的机器人模型和传感器话题与 TurtleBot3 默认配置不同,需要修改 Nav2 的参数文件。参数文件通常是一个 YAML 文件,里面配置了机器人半径、规划器插件、代价地图话题等。
4.7 运行任务并验证
启动导航后,先确认让 Nav2 定位准确。建议使用 rviz2 里的2D Pose Estimate按钮,手动给机器人一个初始位置。
然后运行主流程:
ros2 run clean_robot clean_task预期现象:
- 机器人先规划一条从当前位置到房间入口的路径。
- 到达入口后,机器人开始沿直线来回移动。
- 移动期间,可以打开另一个终端执行
ros2 topic echo /clean_state,看到cleaning_start和cleaning_done两个状态。 - 清扫完成后,机器人重新调用 Nav2,规划回充电桩的路径。
整个流程跑通后,你就已经完成了一个最简的“真·智能保洁”闭环。后续可以继续优化清扫路径覆盖率、加入拖布升降控制、接入语音指令等。
5. 常见问题与排查思路
在跑这个演示的过程中,你会遇到不少报错。下面是我整理的高频问题,按“现象 → 原因 → 解决”的顺序列出。
5.1 TF 树报错:找不到 map 到 base_link 的变换
现象:
[ERROR] Could not get transform from 'map' to 'base_link'原因:
Nav2 需要知道机器人在 map 坐标系下的位置,这个位置通常由 AMCL 或 SLAM 节点发布。如果定位节点没有启动,或者机器人的 TF 树不完整,就会出现这个错误。
解决思路:
- 先检查 TF 树,运行
ros2 run tf2_tools view_frames生成/tmp/frames.pdf查看。 - 确认
robot_state_publisher是否正常发布base_link到各传感器坐标系的变换。 - 确认
amcl节点是否启动,并正确订阅scan和map话题。 - 优先使用 Nav2 自带的
nav2_bringup启动,避免手工漏掉配置文件。
5.2 导航目标一直失败,机器人原地转圈
现象:
Nav2 接受了目标,但机器人一直在原地旋转或不断调整方向,无法到达。
原因:
常见原因有两个:一是初始定位不准,机器人认为自己所在的位置和真实位置偏差很大;二是全局路径规划失败,可能是因为膨胀半径设置过大,导致地图上所有通路都被视为不可通行。
解决思路:
- 在 rviz2 中用
2D Pose Estimate重新给机器人一个准确的初始位姿。 - 打开 Nav2 的代价地图显示,观察地图上是否有大片红色区域阻塞通道。
- 调小
inflation_radius,或者检查地图中的静态障碍物是否过密。 - 查看全局规划器的规划结果,确认
plan话题上是否有路径发布。
5.3 弓字形清扫时机器人越走越偏
现象:
清扫开始后的前面几米还正常,后面逐渐偏离直线,甚至撞上墙。
原因:
这是开环控制(open-loop control)的典型问题。机器人轮子打滑、左右轮直径差异细微、地面摩擦不均等,都会导致实际运动轨迹和预期不一致。
解决思路:
- 在清扫时订阅
/odom,实时修正左右轮速度。 - 使用 PID 闭环控制,让实际线速度和角速度靠近目标值。
- 如果清扫精度要求高,建议把弓字形路径拆成多个目标点,再用 Nav2 导航,而不是直接发
cmd_vel。
5.4 地图保存后 Nav2 无法加载
现象:
启动 Nav2 时报错,提示 map 文件无法解析。
原因:
最常见的原因是.yaml文件中的图片路径是相对路径,而 Nav2 的启动目录和执行目录不一致。
解决思路:
在.yaml中把image字段改成绝对路径,例如:
image: /home/your_name/clean_robot_ws/maps/room_map.pgm resolution: 0.05 origin: [-10.0, -10.0, 0.0] occupied_thresh: 0.9 free_thresh: 0.1 negate: 0改完后再重新启动 Nav2。
5.5 仿真环境下机器人穿墙而过
现象:
在 Gazebo 中,机器人没有撞到墙,却直接从墙中间穿过去了。
原因:
这通常不是导航问题,而是物理仿真碰撞失效。Gazebo 的碰撞检测依赖 URDF 中的<collision>标签。如果 URDF 中只写了<visual>没有写<collision>,仿真环境就不会把它当成实体障碍物。
解决思路:
检查 URDF 文件中每个 link 是否都包含<collision>标签。例如:
<link name="base_link"> <visual> <geometry> <cylinder radius="0.10" length="0.05"/> </geometry> </visual> <collision> <geometry> <cylinder radius="0.10" length="0.05"/> </geometry> </collision> </link>5.6 导航到目标点后机器人停止,但清扫节点没反应
现象:
Nav2 显示导航成功,机器人停住了,但没有继续执行弓字形清扫。
原因:
在演示代码中,导航和清扫是两个独立节点。rclpy.spin(nav_client)在导航成功后调用了rclpy.shutdown(),导致后续清扫代码没有机会执行。
解决思路:
不要在每个流程步骤中调用rclpy.shutdown()。改用状态机在单个节点中管理状态:导航中 → 清扫中 → 返航中。清扫功能封装成定时器回调,避免阻塞线程。
一个常见的做法是用asyncio或rclpy的定时器把任务拆散,不要把长时间运行的time.sleep放在回调线程里,否则会阻塞 ROS2 的 executor。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| TF 变换缺失 | 定位节点或 robot_state_publisher 未启动 | 检查 TF 树、确认 AMCL 启动 |
| 目标不可达 | 初始定位不准或膨胀半径过大 | 重新估计位姿,调整 costmap 参数 |
| 清扫偏离 | 开环控制,轮子打滑 | 用里程计闭环或 Nav2 逐点导航 |
| 地图加载失败 | 图片路径错误 | 将 image 改为绝对路径 |
| 穿墙而过 | URDF 缺少 collision | 在仿真模型中加入碰撞体 |
| 清扫不开始 | 节点退出或阻塞 | 改用统一状态机节点管理 |
6. 最佳实践与工程建议
跑通演示只是第一步。如果你想把这个 demo 培养成可落地的产品原型,下面这些工程经验值得认真对待。
6.1 安全永远是最高的优先级
真实服务机器人遇到老人、小孩、宠物、楼梯口时,不能只依赖激光雷达。激光雷达只能扫描一个平面,床单、桌布、台阶边缘、玻璃门都可能是盲区。
工程上建议至少叠加以下几种安全机制:
- 机械急停按钮:物理断开电机电源,不能依赖软件。
- 碰撞传感器/触边防撞条:在激光雷达失效时兜底。
- 深度相机:补足雷达盲区,检测悬空台阶。
- 限速策略:在人员密集区域自动降速到 0.2m/s 以下。
- 远程急停:通过 App 或后台一键停车,防止机器人失控。
任何真实保洁项目,如果只靠“2D 激光雷达 + 算法避障”就推向家庭,是非常危险的。
6.2 传感器标定与时间同步
机器人是多传感器融合系统,激光雷达、IMU、轮式里程计、深度相机各自的时间基准必须统一。不然会出现“雷达已经扫到障碍物,定位模块却认为机器人还好远”的情况。
在 ROS2 中,时间同步通常通过 sensor_filters 或 message_filters 的 ApproximateTimeSynchronizer 实现。对于同一时刻的传感器数据,建议在发送时打上header.stamp,并尽量使用同一个时间源。
如果使用多台设备同时运行,建议部署 NTP 或 PTP 时间同步服务,否则 ROS2 的 TF 和时间戳会出现跳变。
6.3 清扫覆盖率要量化,不能“差不多就行”
做保洁机器人,最核心的指标不是导航成功率,而是清洁覆盖率。工程上建议这样评估:
- 在测试房间地面铺设标记点,统计机器人清扫路径覆盖了多少比例。
- 记录机器人每平方米的清扫时间,判断效率。
- 用干净程度传感器(例如基于地毯/地板灰尘检测)回馈实时清洁效果。
弓字形路径适合简单矩形房间。如果房间有桌子、沙发、柱子,应该把区域划分为多个凸多边形,再分别生成弓字形路径。更进阶的方案是使用全覆盖路径规划算法,例如基于栅格分解的 Coverage Path Planning。这部分内容比导航本身更贴近“保洁机器人”的核心竞争力。
6.4 日志与可观测性
机器人一旦部署到用户现场,没有基本的日志和监控,排查问题会非常痛苦。建议至少做到:
- 所有节点结构化输出日志,字段包含任务 ID、目标点、当前状态、耗时。
- 定期保存 ROS2 bag 文件,记录激光雷达、里程计、TF、导航状态话题。
- 在开发板上运行一个 dashboard,实时展示电池电量、清扫面积、当前任务进度。
- 出现异常时,自动上报到服务器,并保留现场数据用于回放。
不要依赖print()和ros2 topic echo去排查线上问题,那是开发环境才使用的调试方式。
6.5 电池与回充管理
上门保洁机器人的工作时间通常在 1-2 小时以内,电量管理是产品化的关键。一个推荐实现是:
- 电量低于 20% 时,暂停当前清扫任务,保存现场位置。
- 发送导航目标到充电桩。
- 到达充电桩后自动对接充电触点。
- 充电到 80% 以上后,回到刚才的暂停位置继续清扫。
这里的“任务断点恢复”需要一套任务持久化方案,简单做法是把状态写入本地 SQLite 或 ROS2 parameter,复杂做法是引入任务调度中心。
6.6 远程人工接管要设计成“兜底”而非“默认”
前文提到“纯人工”也可以是一种产品策略。但好的产品应该是“机器人先尝试自主处理,遇到不确定情况再请求人工”。具体来说:
- 机器人检测到无法通行的障碍物时,不要反复尝试,而是拍照并上传后台。
- 人工接管的操作需要保留完整的操作日志,避免出现安全事故时无据可查。
- 人工接管结束后,机器人要能恢复到自主导航状态,而不是直接停机断电。
这种人机协同模式,既能提升产品可靠性,又能积累真实场景数据,逐步提高自主比例。
6.7 从仿真到真实环境的落差要提前考虑
仿真环境再完美,也无法模拟真实的线缆缠绕、地毯阻力、暗光反射、宠物遮挡。建议在仿真和真实环境之间增加一个过渡层:
- 先在 Gazebo 中跑通逻辑。
- 再用低成本小车(例如基于 ROS2 的差速底盘)在办公室走廊测试。
- 最后才进入真实家庭环境试用。
每一步都要专门记录“仿真中没遇到、真实环境才出现”的问题清单。这类问题是项目最大的风险来源。
7. 总结与学习路线
回到开头的“天线宝宝”保洁机器人。我们聊的并不是那个穿玩偶服的真人,而是一个严肃的问题:真正实现“机器人上门保洁”,需要把定位、建图、导航、避障、路径覆盖、安全机制、任务调度这些技术全部整合起来。
本文从服务机器人技术栈讲起,重点拆解了 SLAM 和 Nav2 的核心原理,然后带你从零创建了一个 ROS2 工作空间,编写了导航客户端和清扫控制器,最终在仿真环境中跑通了一条“导航到房间入口 → 弓字形清扫 → 返回充电桩”的最小闭环流程。同时还整理了几类高频报错的排查思路。
如果你准备继续深入,建议按这个路径走:
- 完整学习 ROS2 基础,重点掌握节点、话题、服务、action 四类通信方式。
- 阅读 Nav2 官方文档,弄清 costmap、planner、controller、behavior tree 各自职责。
- 尝试自己采集地图,而不是直接使用自带的 simulator map。
- 深入学习全覆盖路径规划,理解弓字形路径、牛耕路径的优缺点。
- 加入真实硬件调试,至少要接触一次差速底盘和激光雷达的联合标定。
- 如果目标是产品化,后续还需要了解多传感器融合定位、即时定位与语义地图、云端任务调度、机器人操作系统安全加固等进阶内容。
这个领域最大的特点就是“看起来简单,实际全是细节”。很多问题只有在现场跑过几十个小时之后才会浮现出来。如果你现在还在仿真阶段,不妨先把本文的示例跑通,再在地图中增加一个椅子或一个纸箱,看看 Nav2 的路径规划会发生什么变化。
本文对你有帮助的话,可以收藏备用。后续我会继续整理服务机器人导航参数的调优笔记、全覆盖路径规划实现、以及 ROS2 日志与监控方案,欢迎保持关注。
祝你调车顺利,早日做出一台“真·智能”的保洁机器人。