1. 从“会扫”到“好用”:自研扫地机器人的核心挑战与破局思路
很多人以为扫地机器人就是“轮子加吸尘器加一块电池”,买几个现成模块拼起来就能跑。我一开始也这么想,直到自己从零搭了一台基于ROS2的扫地机器人,才发现“会扫”和“好用”之间隔着一整套系统工程。会扫,指的是机器能通电、能转、能吸尘;好用,指的是它能在你家客厅里不撞墙、不卡死、不漏扫、不重复扫,还能在你上班时自己规划路线把全屋走一遍。这中间的差距,就是SLAM建图、Nav2导航、传感器融合、行为决策、异常恢复这一整套软件栈的差距。
我做的这台机器,硬件上用的是常见的差速轮底盘、2D激光雷达、IMU、编码器和吸尘模组,软件上跑的是Ubuntu 22.04加ROS2 Humble,建图和定位用SLAM Toolbox,导航用Nav2,最后通过Home Assistant做统一接入和远程控制。整个项目从立项到“好用”状态,前后迭代了四个大版本,踩过的坑比扫过的地还多。这篇文章就把我从“会扫”到“好用”的完整路径拆开讲清楚,包括方案选型、核心参数、实操步骤、问题排查,以及那些只有真正跑过才知道的细节。
如果你正在做类似的自研项目,或者想从零学习SLAM和Nav2在真实机器人上的落地,这篇内容应该能帮你省下不少试错时间。我不会只讲概念,而是把每个环节的“为什么”和“怎么做”都摊开说,让你能直接抄作业。
2. 整体方案设计与核心思路拆解
2.1 为什么选ROS2而不是ROS1
这个项目启动的时候,ROS1 Noetic还在维护期内,但我还是选了ROS2 Humble。原因有三个:第一,ROS2的实时性和多机通信能力更适合机器人产品化,DDS中间件让话题、服务、动作的通信更稳定,不会像ROS1那样依赖中心化的roscore;第二,Nav2是ROS2原生的导航栈,功能比ROS1的move_base更完整,行为树架构让导航逻辑更容易扩展;第三,Home Assistant对ROS2的支持越来越成熟,通过MQTT桥接可以很方便地把机器人状态接入智能家居系统。
当然,ROS2的学习曲线确实比ROS1陡一些,尤其是DDS配置和生命周期节点这些概念。但如果你要做的是“好用”的机器人,而不是实验室里跑一次的demo,ROS2是更值得投入的选择。我实测下来,ROS2 Humble在树莓派4B和Jetson Orin Nano上都能稳定运行,内存占用比ROS1略高,但换来的通信可靠性和多机协同能力完全值得。
2.2 传感器选型:2D雷达还是3D雷达
热词里有人提到“nav2导航使用3d雷达”,我一开始也考虑过3D雷达方案。但实际用下来,对于扫地机器人这种低矮、室内、结构化环境,2D单线激光雷达的性价比和稳定性明显更高。3D雷达的优势在于能感知高度信息,比如识别桌腿和悬空障碍物,但代价是数据量大、算力要求高、成本翻倍。我最终用的是2D雷达加一个朝前的红外防撞和悬崖传感器,配合IMU做姿态估计,整体成本控制在可接受范围内。
如果你确实需要3D感知,比如家里有低矮沙发或者复杂地形,可以考虑在2D雷达基础上加一个深度相机做视觉SLAM辅助。但要注意,视觉SLAM对光照变化很敏感,晚上关灯后基本失效,所以不能完全替代激光雷达。我的建议是:2D雷达做主力建图和定位,深度相机做障碍物识别补充,两者通过ROS2的topic做数据融合。
2.3 建图方案:SLAM Toolbox的实战配置
SLAM Toolbox是ROS2生态里最成熟的2D建图方案之一,支持在线建图和离线建图两种模式。我用的在线异步建图模式,机器人一边走一边更新地图,适合第一次全屋探索。配置上主要调这几个参数:resolution设成0.05米,max_laser_range设成雷达实际量程,minimum_travel_distance和minimum_travel_heading控制地图更新频率。实测下来,minimum_travel_distance设成0.3米比较合适,太小会导致地图频繁更新占用CPU,太大则地图细节丢失。
建图过程中有个关键技巧:尽量让机器人匀速走完全屋,避免急转弯和原地打转。因为SLAM依赖里程计和激光匹配,急转弯时里程计误差会累积,导致地图出现重影或错位。我第一版地图就是因为机器人转弯太猛,客厅和走廊的墙壁出现了明显的双层结构,后来重新建图才解决。
2.4 导航方案:Nav2的行为树架构
Nav2的核心是行为树,它把导航任务拆成一系列可配置的节点,比如计算路径、跟随路径、恢复行为等。默认的navigate_to_pose行为树已经覆盖了大部分场景,但如果你要做全覆盖清扫,就需要自定义行为树或者用nav2_coverage这类插件。我一开始用的是默认行为树,发现机器人在遇到障碍物时只会原地旋转,不会绕行,后来在行为树里加了Spin和BackUp恢复节点,情况好了很多。
Nav2的另一个重点是代价地图配置。全局代价地图用静态地图加膨胀层,局部代价地图用激光雷达实时数据加障碍物层。膨胀半径设成机器人半径加安全余量,我设的是0.25米,实测能避免大部分轻微碰撞。如果家里家具腿比较细,可以适当减小膨胀半径,但要注意留出足够的避障空间。
3. 核心细节解析与实操要点
3.1 ROS2环境搭建与版本选择
Ubuntu 22.04加ROS2 Humble是目前最稳的组合。安装步骤网上教程很多,但有几个细节容易踩坑:第一,rosdep初始化时如果网络环境不好,可以换用国内镜像源,但要注意镜像源的同步延迟;第二,colcon build编译时如果内存不足,可以加--parallel-workers 2限制并行编译数;第三,环境变量source要写进.bashrc,否则每次新开终端都要手动source。
我实测下来,ROS2 Humble在Jetson Orin Nano上编译Nav2需要大约15分钟,在树莓派4B上需要40分钟以上。如果你用树莓派,建议直接装二进制包,不要从源码编译。另外,ROS2的版本要和Ubuntu版本严格对应,Humble对应22.04,Iron对应23.04,不要混用。
3.2 SLAM建图实操:从启动到保存地图
建图流程分三步:启动雷达和底盘驱动、启动SLAM Toolbox、遥控机器人走完全屋。启动文件里要配置好base_link到laser的TF变换,这个变换的精度直接影响建图质量。我一开始用卷尺量了雷达安装位置,后来发现还是用tf2_ros的静态变换发布器更准,因为卷尺测量有毫米级误差,累积到地图上就是厘米级偏移。
建图时用teleop_twist_keyboard遥控,速度不要超过0.2米每秒,角速度不要超过0.5弧度每秒。走完后用map_saver保存地图,生成.pgm和.yaml两个文件。.yaml里的origin参数是地图原点在全局坐标系中的位置,如果建图时机器人起点不是原点,这个参数需要手动调整。
注意:建图过程中不要移动雷达或底盘上的任何传感器,否则TF变换会失效,地图直接废掉。
3.3 Nav2导航配置:参数调优与行为树定制
Nav2的配置文件主要分三块:nav2_params.yaml、行为树XML和启动文件。nav2_params.yaml里最关键的是代价地图参数和控制器参数。控制器我用的是RegulatedPurePursuitController,它比默认的DWB更平滑,适合扫地机器人的低速场景。desired_linear_vel设成0.15米每秒,lookahead_dist设成0.3米,实测下来转弯时不会过度摆动。
行为树方面,默认的navigate_to_pose_w_replanning_and_recovery.xml已经够用,但如果你要做全覆盖清扫,需要加一个ComputeCoveragePath节点。这个节点不是Nav2自带的,需要自己写或者用第三方包。我用的方案是先用nav2_coverage生成覆盖路径,再用FollowPath动作执行,效果比随机碰撞式清扫好很多。
3.4 八叉树地图与3D导航的取舍
热词里提到“八叉树地图导航”,这其实是3D建图的方案。八叉树地图(OctoMap)能把3D空间划分成体素,适合无人机或者机械臂避障。但对于扫地机器人,2D栅格地图已经足够,因为机器人本身就在地面上跑,不需要感知高度信息。我试过用OctoMap做3D建图,结果发现数据量太大,树莓派跑不动,最后还是回到2D方案。
如果你确实需要3D感知,比如家里有台阶或者悬空障碍物,可以用深度相机加OctoMap做局部避障,但全局导航还是用2D代价地图。这样既能感知高度,又不会拖垮算力。
3.5 Home Assistant接入:MQTT桥接与状态同步
Home Assistant接入ROS2机器人,最稳的方式是通过MQTT桥接。ROS2这边用mqtt_client包把机器人状态发布到MQTT broker,Home Assistant那边用MQTT集成订阅这些话题。我发布的状态包括:电池电量、当前地图、机器人位置、清扫模式、错误码。Home Assistant里可以做一个卡片,实时显示机器人位置和清扫进度。
有个细节要注意:MQTT的QoS等级设成1,保证消息至少送达一次。如果设成0,机器人状态可能会丢;设成2,开销又太大。另外,ROS2和Home Assistant的坐标系要统一,我用的都是map坐标系,这样位置显示不会错位。
4. 实操过程与核心环节实现
4.1 底盘驱动与里程计标定
底盘驱动是整台机器人的基础。我用的是两个带编码器的直流减速电机,通过ros2_control做速度控制。里程计标定分两步:先标定轮距,再标定轮径。轮距标定方法是让机器人原地旋转360度,看IMU的偏航角变化,如果实际旋转角度和IMU读数不一致,就调整轮距参数。轮径标定方法是让机器人直线走1米,看编码器读数,如果偏差超过2%,就调整轮径参数。
我第一版里程计误差很大,走1米实际只走了0.85米,后来发现是轮径参数设错了。标定后误差控制在1%以内,SLAM建图质量明显提升。
4.2 激光雷达数据过滤与TF配置
激光雷达原始数据里有很多噪声,比如玻璃反射、地面反光、动态障碍物。我用laser_filters包做了三层过滤:第一层是距离过滤,去掉太近和太远的点;第二层是角度过滤,去掉机器人自身结构遮挡的角度;第三层是动态过滤,用ScanShadowsFilter去掉移动物体产生的噪点。过滤后数据量减少约30%,SLAM匹配速度提升明显。
TF配置方面,base_link到laser的变换要精确到毫米级。我用的是static_transform_publisher,参数里x y z yaw pitch roll要反复测量确认。如果雷达安装有倾斜,pitch和roll也要设对,否则建图时地面会变成斜面。
4.3 SLAM建图完整流程与参数记录
建图那天我记录了完整流程:先启动ros2 launch slam_toolbox online_async_launch.py,然后启动雷达和底盘,用ros2 run teleop_twist_keyboard teleop_twist_keyboard遥控。速度控制在0.15米每秒,每走2米停一下让SLAM完成一次优化。全屋80平米走了大约25分钟,地图保存后检查了一遍,墙壁直线度很好,没有明显重影。
关键参数记录:resolution: 0.05、max_laser_range: 12.0、minimum_travel_distance: 0.3、minimum_travel_heading: 0.3、map_update_interval: 2.0。这些参数在80平米户型里实测稳定,更大的户型可以适当调大minimum_travel_distance。
4.4 Nav2导航启动与路径规划实测
导航启动文件里要加载地图、启动AMCL定位、启动Nav2导航栈。AMCL的参数里laser_model_type设成likelihood_field,max_beams设成60,min_particles设成500,max_particles设成2000。实测下来,AMCL在激光雷达数据稳定时定位精度在5厘米以内。
路径规划我用的是SmacPlannerHybrid,它比默认的NavFn更平滑,适合差速轮底盘。规划出的路径曲率连续,机器人跟踪时不会频繁启停。实测从客厅到卧室的路径规划时间约50毫秒,完全满足实时性要求。
4.5 全覆盖清扫路径生成与执行
全覆盖清扫是扫地机器人的核心功能。我用的方案是nav2_coverage加自定义行为树。nav2_coverage用boustrophedon算法生成弓字形路径,然后通过FollowPath动作执行。路径间距设成机器人宽度的80%,保证不漏扫。实测80平米户型清扫时间约35分钟,覆盖率95%以上。
有个细节要注意:清扫过程中如果遇到动态障碍物,比如人走动,Nav2会重新规划路径,但nav2_coverage生成的路径不会自动更新。我的做法是在行为树里加一个ClearEntireCostmap节点,遇到障碍物时先清代价地图再重新规划。
5. 常见问题与排查技巧实录
5.1 SLAM建图常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 地图重影 | 里程计误差大 | 检查轮径和轮距参数 | 重新标定里程计 |
| 地图错位 | TF变换不准 | 检查base_link到laser的变换 | 重新测量安装位置 |
| 地图缺失 | 雷达数据被过滤 | 检查laser_filters配置 | 调整过滤参数 |
| 建图卡顿 | CPU占用过高 | 检查minimum_travel_distance | 调大更新距离 |
5.2 Nav2导航常见问题与解决
导航中最常见的问题是机器人原地打转或者卡在角落。原地打转通常是代价地图膨胀半径太大,导致机器人认为周围都是障碍物。解决方法是减小膨胀半径,或者调整inflation_radius和cost_scaling_factor。卡在角落通常是恢复行为不够,可以在行为树里加BackUp和Spin节点,让机器人先退出来再重新规划。
另一个常见问题是定位丢失,机器人突然不知道自己在哪。这通常是AMCL粒子数不够或者激光雷达数据匹配失败。解决方法是增大max_particles,或者检查雷达是否被遮挡。我实测下来,AMCL在激光雷达被遮挡超过30%时就会定位丢失,所以雷达安装位置要尽量高,视野要开阔。
5.3 ROS2通信与DDS配置避坑
ROS2的DDS配置是个大坑。默认的FastDDS在树莓派上跑久了会内存泄漏,后来换成CycloneDDS就稳定了。配置方法是在~/.bashrc里加export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp。另外,多机通信时要注意ROS_DOMAIN_ID要一致,否则话题发现不了。
还有个细节:ROS2的话题、服务、动作在跨机器通信时,如果网络环境复杂,可能会丢包。我的做法是用ros2 topic hz检查频率,如果频率不稳定,就改用ros2 topic echo看数据内容,确认是网络问题还是节点问题。
5.4 Home Assistant接入常见故障
Home Assistant接入ROS2机器人时,最常见的问题是MQTT连接断开。排查方法是先检查MQTT broker是否正常运行,再检查ROS2这边的mqtt_client是否发布成功。我遇到过MQTT broker重启后ROS2节点没有自动重连,后来在mqtt_client配置里加了reconnect_interval参数才解决。
另一个问题是状态更新延迟。Home Assistant的MQTT集成默认是异步更新,如果机器人状态变化频繁,可能会丢消息。解决方法是把QoS设成1,并且在Home Assistant里用automation做状态缓存,避免频繁刷新UI。
5.5 独家避坑经验分享
第一个坑:雷达安装高度。我一开始把雷达装在离地10厘米的位置,结果建图时扫到了地毯边缘,地图上出现一圈噪点。后来把雷达升到15厘米,噪点消失。第二个坑:底盘轮子打滑。地毯上轮子打滑会导致里程计误差暴增,SLAM直接崩。后来换了带花纹的橡胶轮,情况好转。第三个坑:电池电压下降导致电机转速变化。锂电池从满电到没电,电压从12.6伏降到10伏,电机转速会下降约15%,里程计如果不做电压补偿,建图误差会累积。我的做法是在ros2_control里加电压反馈,动态调整速度指令。
6. 从“会扫”到“好用”的最后一公里
6.1 异常恢复与自诊断机制
“好用”的机器人必须能自己处理异常。我加了三个自诊断机制:第一,雷达数据超时检测,如果超过1秒没有新数据,就触发急停并报警;第二,里程计异常检测,如果编码器读数突变超过阈值,就判定为打滑并重新定位;第三,电池低压保护,电压低于10.5伏时自动回充。这些机制通过ROS2的生命周期节点管理,启动时按顺序初始化,异常时按优先级处理。
6.2 清扫策略优化:从随机到规划
第一版清扫用的是随机碰撞式,效率极低,80平米要扫1个多小时。第二版改成弓字形规划,时间降到35分钟。第三版加了分区清扫,把全屋分成客厅、卧室、厨房三个区域,每个区域单独规划路径,避免重复扫。第四版加了动态避障,遇到人走动时暂停清扫,等人走了再继续。实测下来,第四版的清扫效率比第一版提升了3倍。
6.3 远程控制与自动化联动
通过Home Assistant,我做了几个自动化场景:第一,离家模式,机器人自动开始清扫;第二,回家模式,机器人自动回充;第三,夜间模式,机器人只扫客厅不扫卧室,避免噪音。这些场景通过MQTT消息触发,ROS2这边用mqtt_client订阅对应话题,收到消息后调用Nav2的NavigateToPose动作。
6.4 后续扩展方向
这台机器目前已经能稳定完成全屋清扫,但还有几个方向可以继续优化:第一,加视觉识别,区分地面垃圾类型,调整吸力;第二,加语音控制,通过Home Assistant的语音助手直接下指令;第三,加多机协同,两台机器人分工清扫不同区域。这些扩展都需要在现有ROS2架构上做增量开发,不会推翻重来。
我个人在实际操作中的体会是,自研扫地机器人最难的不是某个单点技术,而是把SLAM、Nav2、传感器、行为决策、异常恢复这些模块整合成一个稳定系统。每个模块单独跑都没问题,但一起跑就会互相干扰。解决方法是模块化设计加充分测试,每个模块先单独验证,再逐步集成。最后再分享一个小技巧:建图时用手机录屏记录机器人走位,后期对照地图排查问题,比看日志直观得多。