
简介本资源是一个面向高校机器人方向课程设计与毕业设计的自动化仓储分拣仿真平台聚焦ROS 2机器人开发与Gazebo三维仿真技术解决仓储场景下路径规划、视觉识别、机械臂抓取、多传感器融合等核心问题。压缩包共346个文件含64个Python脚本实现moveit2运动规划、blob_tracking视觉追踪、line_following循迹控制等、68个STL/DAE模型文件用于Alve机器人及货架建模、21个SDF/URDF/SRDF描述文件定义机器人物理与运动学特性、20个C节点及10个YAML配置文件支撑mecanum_drive_controller、perception_ros2等模块整体大小为101.06MB。已有72人学习下载适用于具备Linux基础与ROS入门经验的学习者。读者可直接运行完整仿真流程获得从环境搭建、传感器模拟深度图、RGB图像、目标检测custom_msgsadvanced_perception、到抓取执行simple_grasping的全链路实践能力并基于alve_description与setup_assistant快速扩展新任务。 这个项目最初的动机特别朴素我所在的团队想验证一套仓储分拣调度算法但直接上产线测试的成本实在太高——一台AGV小车的调试时间、场地占用、安全风险都是问题。于是我把整套流程搬进了仿真环境用ROS 2作为通信骨架用Gazebo搭建出一个可以反复折腾的自动化仓储分拣模拟平台。这套平台跑通了从建图、导航到机械臂抓取分拣的完整链路也帮我避开了大量现场调试的坑。如果你正在做AGV调度、机械臂抓取或者仓储物流仿真相关的项目这篇文章就是一套可以直接抄作业的实战记录。1. 先搞清楚这个模拟平台到底在模拟什么1.1 从一张分拣业务场景图说起想象一个真实的仓储分拣场景一批待分拣的货物从入口暂存区进入AGV小车需要把货架上的物料运输到不同的分拣工位机械臂在指定工位把货物抓起来放到对应料箱里。这套流程里其实揉合了两个核心子问题AGV如何安全地到达指定位置机械臂如何稳定地完成抓取仿真平台要覆盖的就是这两条链路。我在Gazebo里搭了一个带货架、传送带、分拣工位的仓库用URDF建模了一个差速驱动机器人后面统一叫它AGV车体上装了激光雷达和RGBD相机。机械臂部分最开始用了一个简化的吸盘结构后来换成了经典的Panda机械臂模型。整个平台的目标就是把任务下发、AGV导航、机械臂抓取、物料放置这套完整流程在仿真里复现出来而且每一步都能通过ROS 2话题看到实时数据。1.2 为什么必须先在仿真里跑一遍这个问题我在项目答辩时被问过很多次。真实AGV的调试周期有多长以我的经验一次导航避障参数调整从改代码到上真车验证每一步都涉及安全员确认、场地清理、硬件状态检查一个下午能跑三组实验就算快了。而且真车测试遇到碰撞轻则检修重则损坏传感器一次意外就能耗掉一周时间。仿真平台的价值在于三个字可复现。算法改了一个参数真实场景可能需要跑十次才能判断效果仿真里分分钟就能跑完同等规模的测试。更重要的是很多危险场景在真机上根本不敢测比如AGV贴着货架边缘高速转弯、机械臂末端接近障碍物时急停这些在Gazebo里都是随手可以构造的测试用例而且可以反复碰撞、反复验证安全策略。1.3 技术选型为什么是ROS 2加Gazebo的组合做选型的时候我重点对比过三套方案。第一套是ROS 1加Gazebo 11这个组合最成熟社区资料也多但ROS 1已经停止版本迭代了新项目没必要再往旧生态里投。第二套是MuJoCo加自研强化学习环境物理引擎确实快但场景编辑器太弱想搭一个带货架、传送带和机械臂的完整仓库很费劲。第三套是Isaac Sim渲染效果和物理精度都很好但生态偏封闭而且对显卡要求太高我们团队有不少同学还在用核显笔记本。最终选了ROS 2加Gazebo这个组合主要有三个理由一是Gazebo和ROS生态的集成天然顺畅传感器仿真、插件机制、话题桥接都有现成方案二是开源社区资源足够丰富遇到问题基本都能搜到答案三是ROS 2本身采用的分布式通信机制DDS对多AGV协同场景更友好后续从单机扩展成多机也顺理成章。下面这个表格可以帮大家快速做个对比方案场景建模能力机械臂规划支持传感器仿真学习成本硬件要求ROS 2 Gazebo强SDF格式灵活MoveIt 2集成成熟完善支持GPU加速中等较低ROS 1 Gazebo 11强MoveIt较老兼容性一般完善较低较低MuJoCo弱建模麻烦需自研接口简单中等较低Isaac Sim强材质精美集成良好完善高高1.4 平台的整体架构长什么样整个平台我按四层来设计。底层是仿真场景层也就是Gazebo世界里的仓库场地、货架、货物模型、传送带这一层决定了机器人看到什么。第二层是机器人层包含AGV底盘的URDF模型、激光雷达和RGBD相机的传感器配置、机械臂的模型和驱动插件。第三层是算法层负责建图的slam_toolbox、负责路径规划的Nav2、负责机械臂运动规划的MoveIt 2都挂在这一层。最上面是调度层一个自定义的状态机节点负责任务队列管理控制整个分拣流程的推进。项目打包后的目录结构大概是这样的warehouse_sorting_platform/ ├── worlds/ │ └── warehouse.world ├── models/ │ ├── agv/ │ ├── shelf/ │ └── cargo_box/ ├── urdf/ │ └── agv.xacro ├── src/ │ ├── agv_bringup/ │ ├── agv_navigation/ │ └── sort_controller/ └── config/ ├── slam_toolbox.yaml └── nav2_params.yaml调度层和算法层之间的消息流是典型的链式结构调度节点发布导航目标点给Nav2Nav2控制AGV到达位置后回报完成事件调度节点再触发机械臂的抓取动作抓取完成后继续下发下一个任务。这套链路跑通之后整个分拣平台就有了最基本的闭环。2. 环境准备Ubuntu 24.04加ROS 2 Jazzy加Gazebo Harmonic的版本搭配2.1 版本匹配是第一个翻车点很多人在环境搭建这一步就被卡住了原因是ROS 2和Gazebo的版本对应关系非常严格用错了组合ros_gz_bridge这个关键桥接节点根本起不来。Ubuntu 22.04上常用的组合是ROS 2 Humble加Gazebo Fortress而Ubuntu 24.04对应的则是ROS 2 Jazzy加Gazebo Harmonic。我测试机上装的是Ubuntu 24.04所以选的自然是Jazzy加Harmonic这条线。操作系统ROS 2发行版Gazebo主版本桥接包Ubuntu 22.04HumbleFortress (gz fortresses)ros-humble-ros-gz-bridgeUbuntu 24.04JazzyHarmonic (gz harmonic)ros-jazzy-ros-gz-bridge这里有个很容易混淆的点Gazebo Classic也就是gazebo 11和Gazebo Ignition系列是两套不同的架构。Harmonic这代产品直接把命令行工具改成了gz开头很多旧教程里的gazebo命令已经变得不完全适用。所以搜资料的时候一定要认准版本号看到是Fortress或Harmonic相关的教程才拿过来参考。2.2 虚拟机还是物理机如果你手头只有Windows电脑也可以用虚拟机来搭。我前期调环境就是在一台虚拟机上完成的给虚拟机分配了4核CPU、8GB内存和60GB磁盘空间并且在虚拟机设置里开启了3D加速。这里要特别强调一下Gazebo渲染非常吃GPU资源如果不开启3D加速后面加载带纹理的仓库场景会卡到几乎无法操作。另外还遇到过一个问题虚拟机里Gazebo的GPU加速偶尔会失效表现为场景中的模型变成透明或者黑色块。这时候可以在启动Gazebo之前设置环境变量LIBGL_ALWAYS_SOFTWARE1强制走软渲染虽然帧率会下降但至少能保证画面正常。真机环境下建议用一台独立显卡的台式机来做体验会好很多。2.3 安装步骤和ros_gz_bridge的作用环境搭建的完整命令如下sudo apt update sudo apt install ros-jazzy-desktop sudo apt install ros-jazzy-ros-gz-bridge sudo apt install gz-harmonic安装完成后记得把ROS 2的环境变量写入~/.bashrcecho source /opt/ros/jazzy/setup.bash ~/.bashrc source ~/.bashrc这里最关键的是ros_gz_bridge这个包。它的作用是搭一座桥把Gazebo仿真器内部传输的话题和ROS 2的话题连接起来。没有它你在Gazebo里放十个激光雷达ROS 2节点也收不到一个数据。2.4 快速验证环境是否打通装好环境之后别急着搭场景先跑一个最小Demo验证数据链路。我通常的做法是启动一个空世界加一个简单差速小车模型然后在另一个终端执行ros2 topic list如果能看到/cmd_vel、/odom、/scan这一类话题说明ROS 2和Gazebo的桥接已经工作正常。再用ros2 topic echo /odom看一下消息内容确认有数据在流动环境就算通了。这一步看起来很基础但能帮你把环境问题和后面项目代码的问题彻底隔离开排查故障时能省下大量时间。3. 搭建仿真仓库货架、传送带和分拣口怎么建模3.1 场景需求先想清楚再动手仿真场景不是随便摆几个方块就完事你得先明确分拣流程需要哪些功能区。我在设计时把仓库划分为四个区域入口暂存区用来放置待分拣的货物货架区存放不同种类的物料箱分拣工位机械臂和AGV在这里交互出料口按照物料类型分流。每个区域在Gazebo世界里对应一组模型和固定的坐标范围这样后续在调度层写坐标点的时候才不会乱。比如说我在世界坐标系里让入口暂存区位于x轴负方向区域货架区在x轴正方向分拣工位在中心偏左出料口在中心偏右。这样AGV从左边的暂存区取货经过中间的导航通道到达右边的分拣工位动线清晰不容易绕路。3.2 SDF世界文件的结构Gazebo Harmonic直接使用SDF格式的世界文件。一个世界文件的基本结构是sdf标签内嵌套world里面可以包含多个include引用外部模型也可以直接用model标签内联建立模型。下面是我写的一个简单货架模型的片段sdf version1.9 world namewarehouse include urimodel://sun/uri /include model nameshelf_01 statictrue/static link namebase collision namecollision geometry box size1.5 0.8 2.0/size /box /geometry /collision visual namevisual geometry box size1.5 0.8 2.0/size /box /geometry material ambient0.6 0.4 0.2 1/ambient /material /visual /link pose2.0 0.0 1.0 0/pose /model model nameground_plane statictrue/static link namelink collision namecollision geometry plane normal0 0 1/normal size30 30/size /plane /geometry /collision visual namevisual geometry plane normal0 0 1/normal size30 30/size /plane /geometry /visual /link /model /world /sdf这里有三个细节值得注意。第一货架这类不会移动的物体一定要加statictrue/static否则Gazebo会计算它的物理动态浪费计算量还可能出奇怪的现象。第二尽量把collision尺寸和visual尺寸保持一致很多穿模问题就是因为碰撞体和视觉体不一致导致的。第三地面模型用plane而不是一个很大的box这样可以避免不必要的碰撞计算。3.3 货物模型和动态交互货物箱我用一个带颜色的立方体来表示。最简单的方式是单独建一个cargo_box.sdf文件定义一个0.3米见方的箱子然后通过include引用。为了区分不同物料类型我给不同货物设置了不同的材质颜色比如红色箱子代表A类物料蓝色箱子代表B类物料。这样后续在分拣逻辑里就能根据视觉识别的颜色来判断该把哪个箱子分到哪个出口。如果想让货物在传送带上运动还需要在货物和传送带之间建立一个joint。这个关节多半是固定关节因为传送带本身通过表面摩擦力带动货物运动。我试过给箱子加plugin模拟滑动摩擦但效果非常不稳定后来干脆让传送带表面带一点微小的凸起几何体来提供摩擦力效果反而好很多。3.4 摩擦系数和物理参数调优仓储场景里有个很坑的地方是货物从AGV上滑落。AGV驮着箱子转弯的时候箱子会像坐滑梯一样从车顶滑下去。这个问题的本质是箱子和车体表面的摩擦系数不够。在SDF里每个碰撞体都可以设置摩擦参数surface friction ode mu1.0/mu mu21.0/mu2 slip10.01/slip1 slip20.01/slip2 /ode /friction /surfacemu和mu2分别代表两个方向的摩擦系数值越大表面越粗糙。我一开始用默认的0.5箱子在转弯时必掉调到1.0之后稳定性明显改善。如果箱子还是滑落可以检查一下箱子的质量是否过小同样的摩擦系数下质量太小的物体受到侧向力时更容易滑动。4. 移动平台建模差速底盘、RGBD相机和激光雷达的配置4.1 URDF/XACRO整体设计AGV的模型我用XACRO格式编写这是URDF的宏版本支持参数复用后期改轮距、改轴距只需要改一个宏变量。整体结构分五部分底盘(base_link)、两个驱动轮(left_wheel和right_wheel)、两个万向支撑轮(caster_1和caster_2)、一个激光雷达传感器安装座(lidar_link)和两个RGBD相机安装座(camera_front和camera_back)。XACRO文件的基本骨架如下?xml version1.0? robot nameagv xmlns:xacrohttp://www.ros.org/wiki/xacro xacro:property namewheel_base value0.4 / xacro:property namewheel_radius value0.1 / xacro:property namewheel_width value0.05 / link namebase_link inertial mass value10.0/ inertia ixx0.1 ixy0.0 ixz0.0 iyy0.15 iyz0.0 izz0.1/ /inertial visual geometrybox size0.6 0.4 0.15//geometry /visual collision geometrybox size0.6 0.4 0.15//geometry /collision /link link nameleft_wheel visual geometrycylinder radius${wheel_radius} length${wheel_width}//geometry /visual collision geometrycylinder radius${wheel_radius} length${wheel_width}//geometry /collision /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 ${wheel_base/2} 0 rpy${-pi/2} 0 0/ axis xyz0 0 1/ /joint /robot写URDF时有不少细节需要留意。每个link最好都加上inertial参数虽然Gazebo在缺少惯性参数时会自动推断但推断出来的值经常不符合实际导致小车在加速、转弯时姿态异常。关节的axis决定了轮子的旋转轴方向差速轮的旋转轴要按照ROS的标准设为xyz0 0 1配合rpy把轮子模型竖起来这块很容易搞错。4.2 差速驱动插件配置Gazebo里驱动差速底盘最常用的插件是gz_ros2_control配合ackermann或diff_drive控制器的方式但更简单直接的是使用Gazebo自带的libgazebo_ros_diff_drive.so插件。我把插件配置放在一个单独的agv_gazebo.xacro文件里通过gazebo标签扩展。gazebo plugin namediff_drive filenamelibgazebo_ros_diff_drive.so ros namespace/agv/namespace /ros left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation${wheel_base}/wheel_separation wheel_diameter${wheel_radius * 2}/wheel_diameter max_wheel_torque20/max_wheel_torque max_wheel_acceleration1.0/max_wheel_acceleration command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic odometry_frameodom/odometry_frame robot_base_framebase_link/robot_base_frame /plugin /gazebo插件里command_topic设置为cmd_vel这样Nav2下发的速度指令就能直接驱动小车运动。odometry_topic设置为odom插件会自动计算左右轮转速并发布里程计数据同时还会发布odom到base_link的TF变换这个变换是导航定位的基础。4.3 激光雷达和RGBD相机配置激光雷达我用了GPU版本的ray传感器这样可以把射线检测计算放到GPU上场景复杂时性能压力会小很多。雷达的扫描范围和分辨率按实际调试需要来调我设置的是360度扫描、0.5度角分辨率更新频率10Hz。RGBD相机用的是Gazebo的深度相机插件前后各装一个。相机的分辨率、水平视场角、更新频率都直接影响感知效果。一个关键参数是depth_near_clip和depth_far_clip分别表示相机能感知到的最近和最远距离。室内仓储场景下最近距离设0.2米、最远距离设10米基本够用。相机插件的配置片段如下gazebo referencecamera_front_link sensor typedepth namecamera_front_sensor update_rate10/update_rate camera horizontal_fov1.047/horizontal_fov image width640/width height480/height /image depth_camera min_depth0.2/min_depth max_depth10.0/max_depth /depth_camera /camera plugin namecamera_front_driver filenamelibgazebo_ros_camera.so ros namespace/agv/namespace remappingimage:camera_front/image_raw/remapping remappingdepth:camera_front/depth/remapping /ros /plugin /sensor /gazebo4.4 用ROS 2话题验证数据链路模型写完启动Gazebo之后第一件事不是急着建图导航而是验证整条数据链路是否通畅。我写了一个简单的Python节点作为数据转发哨兵订阅底盘的里程计和激光雷达话题同时发布一个测试速度指令看看指令能不能让小车动起来。import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry from sensor_msgs.msg import LaserScan class DataRelayNode(Node): def __init__(self): super().__init__(data_relay_node) self.cmd_pub self.create_publisher(Twist, /agv/cmd_vel, 10) self.odom_sub self.create_subscription(Odometry, /agv/odom, self.odom_callback, 10) self.scan_sub self.create_subscription(LaserScan, /agv/scan, self.scan_callback, 10) def odom_callback(self, msg): self.get_logger().info(fodom: x{msg.pose.pose.position.x:.2f}, y{msg.pose.pose.position.y:.2f}) def scan_callback(self, msg): self.get_logger().info(fscan: angle_min{msg.angle_min:.2f}, ranges_size{len(msg.ranges)}) def send_test_velocity(self): twist Twist() twist.linear.x 0.2 twist.angular.z 0.1 self.cmd_pub.publish(twist) def main(argsNone): rclpy.init(argsargs) node DataRelayNode() node.send_test_velocity() rclpy.spin(node) rclpy.shutdown() if __name__ __main__: main()运行这个节点后如果终端里能持续打印出odom坐标和scan数据说明底盘驱动、传感器仿真、ROS 2话题通信全部正常。这一步是整个平台的冒烟测试后面所有算法都建立在这条数据链路上。顺便说一下ros2 topic hz /agv/scan这个命令用来查看话题发布频率很有用如果频率远低于配置里的10Hz多半是仿真性能跟不上了。5. 让AGV自主移动slam_toolbox建图与Nav2导航5.1 先建图再导航顺序不能乱很多新手把建图和导航混在一起搞结果地图没建好导航自然跑不通。我的做法是把流程拆成两个阶段先用slam_toolbox在线建图得到一张2D栅格地图并保存下来然后启动Nav2加载这张地图配合AMCL定位让AGV能在已知地图里自主规划路径。这个拆分的好处是建图阶段发现环境建模有问题可以直接改世界文件导航阶段排查问题时不需要反复重建地图效率高很多。5.2 slam_toolbox配置和实操流程slam_toolbox是一个高效的2D SLAM工具包在ROS 2里的配置相比老牌Gmapping要简洁很多。我用的配置文件中几个关键参数如下slam_toolbox: ros__parameters: use_sim_time: true odom_frame: odom map_frame: map base_frame: base_link scan_topic: /agv/scan mode: mapping启动slam_toolbox之后让AGV在仓库里逛一圈。最简单的方式是先写一个键盘控制节点或直接发布cmd_vel指令让AGV沿着固定轨迹运动。我在实际操作中喜欢先手动遥控AGV走一个之字形路线把货架区和分拣工位都扫一遍这样生成的地图质量最高。建图完成后用命令保存地图ros2 run nav2_map_server map_saver_cli -f ~/warehouse_map这条命令会生成warehouse_map.pgm和warehouse_map.yaml两个文件。pgm是灰度图像黑色部分代表障碍物白色部分代表可通行区域yaml记录地图的分辨率、原点、坐标系等元信息。两个文件都要保留后面Nav2加载地图时两者缺一不可。5.3 Nav2导航配置要点Nav2本质上不是一个单节点而是一组节点的集合包括amcl定位、planner_server路径规划、controller_server轨迹跟踪、behavior_server行为控制等。启动Nav2之前需要准备几个关键配置文件planner和controller的代价地图参数、AMCL定位参数以及全局导航行为树参数。最省事的办法是直接用Nav2官方仓库里的参数模板然后做少量修改。我改得最多的三个参数是参数默认值我的配置作用robot_base_framebase_linkbase_link机器人本体坐标系scan_topicscan/agv/scan激光雷达数据来源transform_tolerance0.10.5TF变换容差仿真时网络延迟大需调大transform_tolerance这个参数值得特别说明。仿真环境下因为Gazebo的频率和ROS 2节点的处理速度不完全同步TF变换偶尔会延迟默认的0.1秒容差经常导致Nav2报无法获取有效变换的错误我把这个值调到0.5秒之后就稳定了很多。5.4 让AGV自动跑起来Nav2启动后最直观的验证方式是先用RViz可视化工具在地图上给AGV指定一个目标点看路径规划是否正常、小车能否平稳驶达。RViz里选中Nav2的2D Goal Pose按钮在地图上点一个目标位置并设置朝向AGV就会自动规划路径并开始移动。如果要从程序层面控制可以用Nav2的Simple Commander API发目标点。我在分拣调度节点里就是通过这个接口来下发导航任务的代码大概是这样import rclpy from rclpy.node import Node from geometry_msgs.msg import PoseStamped from nav2_simple_commander.robot_navigator import BasicNavigator class NavigationClient(Node): def __init__(self): super().__init__(navigation_client) self.navigator BasicNavigator() def send_goal(self, x, y, yaw): goal_pose PoseStamped() goal_pose.header.frame_id map goal_pose.header.stamp self.get_clock().now().to_msg() goal_pose.pose.position.x x goal_pose.pose.position.y y goal_pose.pose.orientation.z yaw self.navigator.goToPose(goal_pose)这里要注意下发目标点前必须等Nav2完全启动否则会报action server not available的错误可以在代码里加一个等待导航action server激活的判断逻辑。6. 机械臂分拣MoveIt 2与Gazebo联动6.1 把MoveIt 2和Gazebo接起来的关键一步电脑前的你可能已经在网上看到过一大堆只跑MoveIt 2的Demo那些Demo里机械臂确实能动但每一步都是模拟出来的运动学计算和真实物理毫无关系。要在Gazebo仿真里让机械臂真正抓取物体必须通过ros2_control做一层桥接把MoveIt 2规划出来的关节轨迹下发给Gazebo模型执行。简单来说MoveIt 2负责想——规划出一条无碰撞的运动轨迹ros2_control负责做——把轨迹里的每个关节角度实时作用于Gazebo里的机械臂模型。这两者之间靠controller_manager这个组件来协调没有它MoveIt 2规划得再好机械臂也是一动不动的。6.2 ros2_control配置我在项目里用的是模拟Panda机械臂它需要配置两个关键控制链关节状态控制器和关节轨迹控制器。ros2_control的YAML配置文件如下controller_manager: ros__parameters: update_rate: 100 joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcaster joint_trajectory_controller: type: joint_trajectory_controller/JointTrajectoryController joint_trajectory_controller: ros__parameters: joints: - panda_joint1 - panda_joint2 - panda_joint3 - panda_joint4 - panda_joint5 - panda_joint6 - panda_joint7 command_interfaces: - position state_interfaces: - position配置完成后启动机械臂和Gazebo再用ros2 control list_controllers查看controller是否已经激活。如果看到joint_trajectory_controller的状态是active说明MoveIt 2和Gazebo的通道已经打通了。6.3 抓取流程的完整逻辑一次完整的抓取动作包含三步规划、执行、夹取。规划阶段MoveIt 2会根据当前机械臂位姿和目标抓取位姿计算出一条无碰撞轨迹执行阶段把轨迹发送给controller_manager机械臂在Gazebo里动起来夹取阶段通过控制夹爪的关节闭合来抓住货物。Panda机械臂默认带了一个指式夹爪我通过panda_finger_joint1和panda_finger_joint2两个关节来控制夹爪动作。夹爪闭合的量需要反复调如果闭合太多两指会穿过货物如果闭合不够货物会掉下来。我在仿真里把夹爪的抓取力设得比实际需要大一些这样抓取稳定性更好。6.4 分拣状态机怎么设计整个分拣平台的调度逻辑我实现为一个简单的状态机包含四个状态IDLE空闲、NAVIGATE导航中、PICKUP抓取中、PLACE放置中。状态机线程的运行逻辑大致是这样的收到分拣任务后从IDLE进入NAVIGATE把目标点下发给Nav2当AGV到达取货点状态切换到PICKUP调用MoveIt 2执行抓取动作抓取成功后进入PLACE状态导航到出货点再执行放置动作放置完成后回到IDLE等待下一个任务。这个状态机用Python写大约三百行就能跑通核心就是一个while循环加一张状态转移表。实践下来最麻烦的不是写状态机本身而是处理各种意外情况比如导航超时、抓取失败、货物滑落。我的建议是每个状态都加上超时判断超时后自动切回IDLE并反馈错误信息这样整个平台的鲁棒性会好很多。7. 我踩过的坑与调参心得7.1 TF坐标系混乱导致雷达数据错位这个坑在我调试过程中出现了不止一次。症状是AGV明明停在原地激光雷达在RViz里显示的点云却不断旋转扭曲Nav2完全无法定位。排查到最后发现是机械臂的URDF模型里某个关节的parent和child写反了导致panda_link0被错误地挂在了panda_link3下面整条运动链的坐标变换全部乱套。排查TF问题有一个很实用的命令链。先用ros2 run tf2_tools tf2_echo map base_link查看两个关键坐标系之间的变换是否稳定再用ros2 run tf2_tools view_frames生成整个TF树的PDF图一目了然地看到各个坐标系之间的层级关系。7.2 小车原地打滑和穿模AGV刚放进仓库场景时给它一个速度指令结果是轮子转了但车丝毫不前进。检查后发现问题出在地面的摩擦系数上。Gazebo默认地面材质虽然看起来是平面但摩擦参数很低尤其是我给地面模型加了plane标签后默认的mu值是0.5对于这个重量的AGV来说根本不够用。在车轮的碰撞体里显式设置摩擦系数为1.2之后问题立刻解决。穿模问题则大多出在碰撞体尺寸小于视觉体尺寸。比如货架视觉体显示的是一个1.5米宽的架子但碰撞体只有1.2米宽AGV导航的时候就会贴着视觉边缘插进模型里。解决办法很简单让碰撞体和视觉体的尺寸保持一致或者稍微放大碰撞体宁可看起来有一点空隙也不能让它穿模。7.3 仿真卡顿严重影响调试效率Gazebo仿真的计算量非常大尤其是同时开激光雷达、两个RGBD相机和机械臂的时候帧率会掉到个位数。这里我有三个优化经验。第一把激光雷达和相机的update_rate从10Hz降到5Hz视觉感知任务完全够用但计算量减半。第二在Gazebo的GUI里关闭实时渲染只保留传感器数据输出调试算法时画面卡但数据是通的。第三可以把非关键模型的visual材质换成简单颜色减少纹理渲染的负担。7.4 MoveIt 2规划不成功机械臂抓取时经常遇到MoveIt 2规划失败报错信息是Trajectory can not be found。这个问题的常见原因有两个一是目标位姿本身就是自碰撞或与环境碰撞的二是MoveIt 2的碰撞检测矩阵把机械臂自身也当成了障碍物。我处理这个问题的方法是在MoveIt 2的配置中通过Planning Scene的API显式设置允许机械臂和自身之间的碰撞对。比如夹爪靠近货架时允许夹爪和货架接触但不允许机械臂其他部位触碰货架。这样既保持了安全性又避免过度保守导致路径搜索失败。7.5 调试工具和个人心得最后分享几个我调试过程中反复用到的工具和经验。ros2 doctor可以在系统环境出问题时自动诊断常见故障虽然有一些误报但用来排查TF异常、参数缺失这类问题很高效。rqt_graph是可视化节点通信结构的利器数据流哪个环节断了一眼就能看出来。ros2 topic hz用来验证话题频率如果某个话题的频率远低于预期大概率是上游节点处理不过来或者仿真负载太高。建图效果不好的时候优先检查AGV运动的平顺性。如果手动控制时速度忽快忽慢建出来的地图一定会有明显的重影或偏移。我后来给键盘控制节点加了一个梯形速度平滑让AGV在加速、减速时更平稳建图质量提升非常明显。至于整个平台后续的扩展方向我目前正在把单AGV场景扩展成多AGV协同用ROS 2的lifecycle节点和action机制来实现任务的多机分派。另外Gazebo Harmonic的插件机制也支持自定义传感器下一步我打算给相机加上二维码识别插件结合仓储场景里面的二维码标签做精确停靠进一步提高分拣流程的定位精度。仿真平台的价值就是这样每改一个想法都能快速验证这种试错速度和自由度真实环境里很难做到。本文还有配套的精品资源点击获取