ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ROS2第五天:功能包创建、通信机制与可视化调试实战

ROS2第五天:功能包创建、通信机制与可视化调试实战 今天是2024年也是我开始学ROS2的第五天。回头看看第一天在Ubuntu 22.04上折腾安装时对着终端里那一堆jammy InRelease错误束手无策的样子和现在能熟练创建一个C功能包、让两个节点通过话题聊起来的状态简直判若两人。很多自学者都会卡在第四天到第五天这个坎上——前三天的环境配置已经耗尽了大部分耐心第四天试着跑官方例程又漏洞百出真正对ROS2有初步掌控感往往就是第五天。我之前在网上搜过不少资料像ros2菜鸟教程、ros2项目实例大多不是太浅就是太散。这篇笔记不打算从零讲环境配置而是聚焦于我从第五天真正开始上手写代码、调通信机制、搞可视化时遇到的那些具体情况和最终解决思路。如果你也已经装好了ROS2并且在创建功能包、理解话题服务动作、用rviz2展示数据这几个环节有点懵那么这篇内容应该能帮你少走半天弯路。1. 安装的终点与工程化的起点第五天的真实状态1.1 为什么装好环境不等于会写代码先聊点虚的。前四天我一直在跟apt源、依赖库作斗争。比如我用鱼香ROS的一键安装脚本避开了不少坑但中途手动添加官方源的时候依旧会因为公钥问题或者网络波动报错文件里记录的获取:1 http://packages.ros.org/ros2/ubuntu jammy inrelease这种片段现在看真是眼泪。很多教程默认你环境装好就万事大吉可在实际使用中可能你写完了代码却因为环境变量没生效导致ros2 run完全找不到包。第五天我决定不再纠结于怎么把环境装得更完美而是直接进入怎么写一个能跑起来的工程。这是个心法层面的转变。如果你一直停留在装完环境跑跑demo不主动去敲colcon build不去起一个自己新建的功能包那你学ROS2的时间跨度可能会被拉长到几周甚至一个月。ROS2跟传统编程不一样的地方在于它的核心是“怎么在一个分布式系统里让各个进程通信”环境只能算入场券工程的构建逻辑才是真正的分水岭。1.2 一天不碰终端那些潜藏的配置就会给你下绊子在正式写代码之前我花了大概半小时把环境重置检查了一遍。很多时候昨天还能用的命令行今天一开新终端就失效了。这是因为ROS2的环境配置不是写进~/.bashrc了就一劳永逸的。如果你像我一样安装了多个版本的ROS2发行版比如主用的Humble备用测试的Jazzy或者用了conda管理Python环境那在source /opt/ros/humble/setup.bash和source ~/ros2_ws/install/setup.bash两者之间稍有疏忽就会遇到明明装了demo_nodes_cpp却报Package demo_nodes_cpp not found的诡异状况。要解决这种问题我有两条经验。第一尽量不要在conda的base环境下直接无脑运行colcon build容易把依赖带到系统层面去第二写一个自己的工作环境初始化脚本把source命令顺序固定下来不要每次都手动输入。另外如果涉及Docker的net模式与端口转发你还需要确认容器里的ROS2共享内存/dev/shm设置正常不然节点之间的通信延迟会高到让你怀疑人生。2. 从看得懂图到写得出代码彻底拆解话题、服务与动作2.1 话题ROS2里的广播电台无人值守但讲究时效热搜词里有ros2话题服务动作这确实是ROS2最核心的骨架。我理解话题最好的方式就是广播电台。发布者就是一个电台DJ它只管对着话筒说话根本不管有没有人听订阅者就是一个收音机只要调到对应频率话题名和消息类型就能收到内容。这个话题机制天然就是为传感器数据比如mavros发出来的imu/data设计的因为这类数据永远在更新你可以随时半路上车接上以后就能拿到最新的一帧。写话题代码时有一个非常容易忽略的点就是消息类型和话题名的匹配。在C里如果你把std_msgs::msg::String发到/chatter然后订阅端非要用sensor_msgs::msg::LaserScan去接收编译能过运行直接崩。在ROS2里命令行的ros2 topic list和ros2 interface show是你的救命稻草。我第五天第一次成功跑通两个自定义节点互发消息时成就感并不高反而是用ros2 topic echo看到数据线上流动时那种系统活了的感觉特别强烈。2.2 服务一问一答的同步请求适合查询和触发话题是持续的流服务则是短暂的一次性问答。就好比你去咨询台问路你问一句XX路怎么走客服回答一句直走右转问完就结束。ROS2的服务使用client-server模型客户端发送请求服务器处理并返回响应。因为这种同步特性它不适合传高频传感器数据但非常适合做开关类控制比如摄像头标定时的/camera/calibrate服务或者机械臂UR5抓取时的开夹爪动作。我实际踩坑的地方在于服务通信里的超时设置。ROS2的Python和C客户端库调用服务时如果不设置超时一旦服务端没启动你的节点调用会一直卡在那里。最好为每次服务调用设置一个timeout比如5秒并在超时后抛出清晰的日志提示。相比话题的开箱即用服务在程序逻辑里更强调异常处理否则一个未启动服务端就能让你的主程序线程直接挂起。2.3 动作长期任务的三段式管理外卖员模式如果说话题是广播、服务是问答那么动作就是派单员和外卖员的关系。动作Action由目标Goal、反馈Feedback和结果Result三部分组成。比如你让机器人从A点导航到B点这是个耗时好几秒甚至几十秒的任务你发一个目标过去机器人会持续给你反馈我走到哪里了最后到达了再告诉你结果成功。实际上ros2路径规划、八叉树地图导航这些上层应用底层大量是基于动作机制做的。我第五天花了不少时间看教程里关于rclpy.action.ActionServer的写法。它比起单纯的话题通信多了一个取消的语义。外卖员走到一半用户取消订单动作客户端能发送取消请求服务端响应取消这是服务做不到的。如果你以后做移动机器人底盘记得一定要优先考虑动作而不是强行用话题加状态机的办法去拼因为动作框架本身已经帮你把任务状态机理清楚了。2.4 三种通信在代码层面选择的心法说了这么多概念落到实操上我总结出一套特别简单的选型原则纯属个人经验分享。如果你不确定用哪种可以问自己三个问题这个数据是连续的、实时的流吗是选话题。这个数据是偶尔请求一下需要立刻给答案吗是选服务。这个任务是持续一段时间的并且需要中途反馈进度吗是选动作。这套原则能让你在写任何节点前少纠结半小时。3. 第五天的高光时刻用C从零创建一个功能包3.1 为什么我选择硬啃C功能包而非Python热搜里既有ros2创建c功能包也有ros2扫盲。我在学ROS2之前Python基础还不错上手rclpy确实快两行代码就能发一个话题。但我第五天决定稍微挑战一下用C编写自己的第一个功能包原因有两点。第一ROS2底层的核心库很多就是C写的用rclcpp能让你更容易理解资源的生命周期比如智能指针的赋值、节点的自旋原理。第二后面做实际机器人项目比如对接UR5这种工业臂性能敏感的控制链基本会是C实现Python更适合跑验证算法而不适合做实时控制。另外C功能包在编译时暴露的问题更多能逼你搞懂ROS2的依赖是怎么找的。用Python包只要把代码放在对应目录几乎就能跑但C有一个明显的CMakeLists.txt配置门槛这也是为什么ros2创建c功能包搜索量一直不小。3.2 CMakeLists.txt里两个必须看清的坑创建功能包的命令没什么好说的ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs my_pkg。等执行完你会发现包的结构很简单。但真正跌跟头的地方在CMakeLists.txt。第一find_package必须和你声明的依赖完全对上。如果你创建包时写了--dependencies rclcpp std_msgs后面又在代码里#include sensor_msgs/msg/imu.hpp却没有在CMakeLists.txt里加find_package(sensor_msgs REQUIRED)编译时你会看到一大堆找不到头文件的红色报错。我第五天第一次遇到这个坑时整个人差点懵掉因为报错指向的是外部库文件怎么看都不是自己写的错误。第二ament_target_dependencies里的库名要大小写敏感。rclcpp在ROS2里是包名但在CMake里引入时写ament_target_dependencies(my_node rclcpp std_msgs)就够了但你还是得保证名字和你find_package时完全一致。此外如果你在src下写了多个节点要在add_executable里分别声明再通过install(TARGETS ... DESTINATION lib/${PROJECT_NAME})安装否则你运行节点时会提示executable not found。3.3rclcpp::spin()阻塞的真相与多线程场景C节点里最经典的一句话就是rclcpp::spin(std::make_sharedMyNode())。第五天我对这个spin函数产生了严重的好奇为什么程序运行到这里就像被粘住了一样其实spin就是进入一个消息处理循环类似于while(true)不断从通信队列里取回调函数并执行。如果只有一个线程做spin那么你如果在回调里写了耗时操作整个节点就会卡住无法处理新的消息。这种时候你应该考虑使用rclcpp::spin(node, executor)或者使用MultiThreadedExecutor。顺带一提第五天我还试着在同一个节点里既当发布者又当订阅者这其实非常常见。比如一个传感器融合节点订阅两个话题用同一个回调做时间同步再发布一个融合结果。这时候用好rclcpp::create_wall_timer做定时发布并结合spin的线程原理就不会遇到回调饥饿的问题。学会了这一点再回头去看别人写的ros2项目实例就能看出门道了不再是瞎猜了。4. rviz2与ros2 bag给机器人装上眼睛和录像机4.1 rviz2显示的不仅是点云和模型还有TF坐标树关键词里的rviz2安装使用ros2、八叉树地图导航把可视化推到了一个无法回避的位置。第五天我装好了rviz2其实安装ROS2桌面版时默认就有了sudo apt install ros-humble-desktop里包含但第一次打开的时候界面空空如也我甚至不知道往哪里添加点云显示。你得在左侧Display面板里Add新增一个PointCloud2或LaserScan然后在Global Options里把Fixed Frame改成map或odom。如果机器人没有正确发布TF变换你加的传感器数据在rviz2里会飘在天上根本不在机器人主体上。我强烈建议所有第五天的学习者手动做一个小任务在终端跑ros2 run tf2_ros static_transform_publisher发布一条从world到base_link的静态坐标变换然后在rviz2里把Fixed Frame设为base_link你马上就能理解机器人模型和传感器数据为什么会呈现在正确的位置上了。TF坐标树是rviz2的灵魂不懂TF你看到的只是散点。4.2ros2 bag record到底记录了什么在跑通节点之后我最想实现的就是ros2 记录数据格式里提到的功能也就是数据录制和回放。因为真实机器人的调试场景里你不可能每次都开着车到处跑把现场数据录成包回到工位电脑上脱机分析是提高效率的法宝。ros2 bag record -a会把所有话题数据录制下来。关键问题来了ros2 bag默认记录出来的格式并不是一本文件名编好的文本它是由bag目录加若干metadata.yaml和.db3SQLite数据库文件组成的。很多人第一次录制完看着文件夹里一堆二进制文件就懵了。实际上这些.db3文件里存放了消息的序列化数据和时间戳索引回放时需要通过ros2 bag play去解析。如果你直接用Python打开db3看到的是一堆数字表格没什么参考价值。4.3 从录制到播放一个完整的调试闭环我建议第五天一定要自己走一遍录制—关闭—播放的闭环。我先跑一个发布方节点另一终端ros2 bag record -o my_bag /chatter大概录5秒。CtrlC停止录制。接着CtrlC关掉发布方节点。然后在终端执行ros2 bag play my_bag再开一个终端ros2 topic echo /chatter你会发现原本已经停止的数据又重新按时间回流出来了。那一刻确实会有种时间倒流的小震撼。这样的调试工具链配合使用能解决一个非常现实的问题有时候你的节点算法与通信强耦合你没法快速定位是传感器数据的问题还是你节点处理逻辑的问题。把数据录下来然后对着同一段包反复跑你的节点代码调整参数这才是工程化调试跑之前必须掌握的技能。5. 那些搜索引擎不会告诉你的第五天经验5.1 用ros2 doctor和日志级别拯救心态五天下来的深切体会是报错不是你的错而是环境信息不透明。ROS2对于遇到崩溃的节点一般在终端里会打出what():后面的异常但很多时候异常原因嵌套在一堆栈调用中。搜索引擎给出的答案往往是复制粘贴命令但命令具体有什么副作用没人说。我刚学的时候遇到问题第一反应是上百度或谷歌搜报错帖搜出来的答案五花八门很容易就越改越乱。后来我开始习惯先看日志级别--ros-args --log-level debug把节点日志级别调到DEBUG往往一些Failed to find rmw的线索就浮现出来了。你可以简单在终端跑ros2 doctor --report这能快速排查出类似环境变量冲突或者网络接口配置异常这类基础问题。5.2 如果要用分布式或仿真至少要知道domain_id和vulcan不能混搭如果你用的是虚拟机比如VMware或VirtualBox并且开了net模式与端口转发那你学ROS2的时候往往会发现宿主机和虚拟机里的节点无法直接通信。因为ROS2的底层默认使用DDS比如FastDDS依赖组播UDP发现对端节点。在虚拟机NAT网络下组播可能会受到限制。解决办法要么改成桥接模式要么设置一个相同的ROS_DOMAIN_ID并确保组播路由正常。同理如果你的项目要跑UR5仿真或是Gazebo最好一开始就把ROS_DOMAIN_ID统一设置比如默认的0或者自定义成42。还有一个可能搜不到的知识点mavros与ros2 imu data的话题桥接。如果你在Simulink或者真实飞控里读过IMU数据再进ROS2会发现消息类型是mavros/imu/data但ROS2原生里没有这个包名它其实是通过mavros自定义的消息接口定义的。处理这类外部设备的常见做法是用ros2 bag record先记录再用自定义节点格式化转发而不是纠结于接口不匹配。5.3 关于ubuntu26.04安装ros2等未来版本的自我提醒搜索词里有ubuntu26.04安装ros2说明很多新手会卡在系统版本和ROS2版本的匹配上。ROS2的发行版和Ubuntu的版本严格对应比如Ubuntu 22.04对应ROS2 Humble勇士目前主流的长期维护版之一。如果你非要装一个官方不支持的组合比如在新版Ubuntu 26.04上强行装老版本ROS2可能会在系统依赖库上出现各种不兼容比如Python版本要求冲突。因此除非你有特殊需求否则老老实实按官方适配表来能省掉你至少一天的时间去排查编译错误。我在学习初期也犯过这样的错误装了Ubuntu 24.04想去凑一个老ROS2版本结果被Qt和OpenSSL版本冲突整到心态爆炸。6. 给正在度过第五天的新手一份尽量别走弯路的心得最后聊聊今天的实操总结和心里话。第五天结束后我做了个简单的思维导图梳理出了接下来几天要继续看的东西。如果你在找教程可以试试在视频站找带运行实例的关键词比如搜ros2菜鸟教程之后只挑那种有完整代码和运行结果录屏的看不要只看讲解PPT。分享几个我今天验证过的实操小贴士都是血泪教训不要在src目录下去做source install/setup.bash要在工作空间的根目录也就是colcon build的同级目录去source不然提示找不到包的报错会把你搞到崩溃。colcon build之后如果你改了代码必须重新colcon build --packages-select 你的包名只重编译改动的包不然每次全量编译会消耗你大量的等待时间。我今天以前因为不知道这个编译选项每次改完代码都要傻等30秒幸运的是我的包很小。如果rviz2启动时显示不出任何东西优先看终端是否提示了The frame map does not exist这时你要么起一个静态TF要么把Fixed Frame改成你消息中header.frame_id对应的那个坐标系比如base_link。至于ros2路径规划和八叉树地图导航我是打算放到第十天以后再碰的因为底层的nav2需要建立在熟悉TF、话题和动作之上。毕竟你现在给我一个真实的差速底盘我如果连/cmd_vel话题都不知道怎么发又怎么可能去规划路径呢。如果你抱着一周学会ROS2的期待我建议把目标放低一点——以第五天能独立实现两个节点之间的话题通信并能在rviz2里看到实时数据流为一个小里程碑就已经远远超过大部分三天打鱼两天晒网的人了。今天先到这办法总比困难多多编译几次多调试几回总会从迷雾里找到出路的。
返回列表