尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ROS1与ROS2深度对比:从架构哲学到实战迁移的完整指南

ROS1与ROS2深度对比:从架构哲学到实战迁移的完整指南 1. 项目概述为什么我们需要重新审视ROS1与ROS2如果你和我一样是从ROS1那个“蛮荒时代”一路摸爬滚打过来的开发者看到“ROS1和ROS2的区别”这个标题可能会觉得这已经是老生常谈了。确实关于两者差异的对比文章网上已经汗牛充栋。但在我实际带领团队完成了几个从ROS1到ROS2的完整迁移项目并且深度参与了基于ROS2的新产品研发后我发现很多所谓的“区别”文章要么停留在官方文档的简单翻译要么就是罗列一堆特性表格看完之后对于“我到底该怎么选”、“迁移的坑在哪里”依然一头雾水。所以这篇“要点记录”不是一份冰冷的特性对照表而是一份来自一线的、带有温度和教训的实战笔记。我会从一个资深机器人软件开发者的视角带你穿透那些表面的参数差异深入到架构哲学、开发体验和生产力影响的层面去理解这场从ROS1到ROS2的范式转移。无论你是一个正在评估技术栈的团队负责人还是一个被新项目“赶鸭子上架”的ROS1老手抑或是刚刚入门不知从何学起的新人我希望这些凝结了真实项目经验和踩坑教训的要点能成为你决策和行动的有力参考。2. 核心架构哲学从“实验室原型”到“生产级系统”的跃迁要理解ROS1和ROS2的区别绝不能只盯着API变了、命令换了这些皮毛。最根本的是它们背后截然不同的设计哲学和目标定位。这直接决定了你在开发中会遇到什么问题以及系统最终能长成什么样子。2.1 ROS1为学术研究而生的“理想国”ROS1诞生于实验室环境它的核心目标是快速搭建机器人原型验证算法。在这个目标下它的设计做出了许多妥协这些妥协在早期极大地促进了机器人社区的繁荣但也为后续的生产化部署埋下了隐患。核心设计中心化的Master节点。这是ROS1的“大脑”和“电话总机”。所有节点Node在启动时都要向Master注册自己发布了或订阅了哪些话题Topic、服务Service。当两个节点需要通信时它们会先去Master那里查询对方的地址然后建立点对点的直接连接。这个设计非常直观就像在一个小团队里有个项目经理Master知道每个人Node负责什么需要找谁对接。带来的便利与问题便利性对于局域网内的原型开发这种设计让动态发现和连接变得极其简单。你可以在运行时随意启动、停止节点系统能自动处理连接关系非常适合算法迭代和调试。单点故障SPOFMaster节点一旦崩溃整个ROS系统的节点发现机制就瘫痪了。虽然节点之间已建立的直接通信不会中断但新的节点无法加入也无法建立新的通信链路。这在需要7x24小时运行的机器人产品上是不可接受的。网络限制ROS1的通信严重依赖多播Multicast和特定的端口如11311。这使得它在复杂的网络环境如多子网、有防火墙的企业网络中部署非常困难几乎无法直接用于广域网或云机器人场景。实时性弱ROS1的通信底层基于TCP/UDP其回调处理在单线程中默认是串行的。一个耗时长的回调会阻塞其他回调的执行无法满足对实时性要求高的控制任务。个人踩坑记录我们早期一个服务机器人项目在展厅演示时偶尔会因为网络波动或某个节点异常退出导致Master不稳定整个机器人“僵住”需要人工重启所有程序。排查起来非常痛苦因为问题现象通信中断和根本原因Master状态异常往往不在一个地方。2.2 ROS2面向产品与分布式系统的“工业框架”ROS2的设计目标非常明确构建可靠的、实时的、可安全部署于生产环境的分布式机器人系统。它从底层开始重构以解决ROS1的固有缺陷。核心设计去中心化的DDS数据分发服务中间件。ROS2摒弃了单一的Master节点引入了成熟的工业标准——DDS作为其通信中间件。DDS本身就是一个完整的、去中心化的发布-订阅框架每个节点都通过DDS中间件来发现和通信。带来的根本性改变无单点故障没有Master系统的鲁棒性极大提升。部分节点或网络设备的故障不会导致整个系统通信瘫痪。网络通透性DDS支持复杂的网络拓扑发现如发现服务器可以轻松跨越子网、甚至互联网进行通信为云机器人、车队管理打开了大门。实时性与确定性ROS2支持配置不同的执行器如多线程执行器允许用户更精细地控制回调的并发执行。结合DDS的QoS服务质量策略可以实现对通信可靠性、截止时间、历史深度等的精确控制从而支持实时控制回路。跨平台与语言支持基于DDS的标准接口ROS2的客户端库如rclcpp, rclpy实现更加规范对Windows、macOS、RTOS如VxWorks, FreeRTOS的支持也更好。哲学总结你可以把ROS1看作是一个精心设计、让研究者能快速上手的“原型开发沙盒”。而ROS2则是一个提供了强大基础工具和材料DDS, QoS但需要你更清晰地规划和设计系统生命周期、节点配置的“工业级建造平台”。从ROS1到ROS2开发者的角色从“沙盒玩家”部分转变为“系统架构师”。3. 通信模型与核心机制的深度对比理解了顶层哲学我们再深入到日常开发中接触最频繁的通信层面。这里的差异直接影响你的代码写法、调试方式和系统行为。3.1 节点发现与生命周期从“放养”到“管理”ROS1 - 动态放养节点启动后自动向Master注册随时可以加入或退出。生命周期管理非常宽松这带来了灵活性但也导致了状态不可控。一个节点可能在初始化资源如连接硬件完成前就开始接收消息从而引发错误。ROS2 - 显式生命周期ROS2引入了**生命周期节点LifecycleNode**的概念。一个生命周期节点拥有明确的几个状态未配置Unconfigured、非活跃Inactive、活跃Active、最终状态Finalized。节点必须在这些状态间显式转换通过服务调用例如只有在“配置”完成后才能“激活”并开始处理数据。这强制开发者思考节点的启动、配置、运行、清理和关闭流程对于管理硬件资源、确保系统安全启动至关重要。# ROS2 生命周期节点状态转换示例概念性代码 # 节点启动后处于 Unconfigured 状态 # 1. 调用 configure 服务节点过渡到 Inactive 状态已分配资源但未运行 # 2. 调用 activate 服务节点过渡到 Active 状态开始执行主逻辑 # 3. 调用 deactivate 服务回到 Inactive 状态 # 4. 调用 cleanup 服务释放资源回到 Unconfigured 状态 # 5. 调用 shutdown 服务进入 Finalized 状态实操心得对于简单的算法节点你可以继续使用普通的Node。但对于任何涉及硬件驱动如相机、雷达、底盘、需要严格管理资源的节点强烈建议使用LifecycleNode。这虽然在开始时增加了复杂度但它能从根本上避免资源竞争、数据丢失等隐蔽问题让系统行为可预测。我们团队在驱动一款工业相机时就曾因未使用生命周期管理导致偶尔启动时丢帧改为生命周期节点后问题彻底消失。3.2 话题与服务性能与灵活性的提升通信协议ROS1使用自定义的TCPROS/UDPROS协议。ROS2基于DDS其协议如RTPS是标准化的效率更高且天然支持多播和复杂的网络路由。服务Service与动作ActionROS1服务是同步的。客户端调用服务后会一直阻塞直到收到响应或超时。这在服务端处理时间长时会卡住客户端。ROS2服务在底层也是异步的尽管API可以提供同步调用的封装。更重要的是ROS2的动作Action得到了极大增强。动作本质上是“可中断、可反馈的长时间服务”。ROS2的动作服务器内置了状态机目标执行中、取消、成功、中止等并且反馈机制更加完善和标准化非常适合导航、机械臂轨迹执行这类任务。3.3 QoS服务质量通信行为的精确控制器这是ROS2相对于ROS1最具革命性的改进之一也是很多初学者感到困惑的地方。在ROS1中通信行为是固定的、黑盒的。在ROS2中QoS策略让你可以像调节旋钮一样精确控制每一次通信的质量。核心QoS策略包括可靠性ReliabilityRELIABLE确保消息按顺序送达类似TCP vsBEST_EFFORT可能丢包类似UDP。对于控制指令你必须用RELIABLE对于高频的传感器数据如点云如果偶尔丢一帧不影响可以用BEST_EFFORT来降低延迟和CPU占用。持久性DurabilityVOLATILE只发送新消息 vsTRANSIENT_LOCAL新订阅者能收到发布者最后发送的若干条历史消息。想象一下地图服务器一个新启动的导航节点需要立刻拿到当前地图TRANSIENT_LOCAL策略就完美解决了这个“先有鸡还是先有蛋”的问题。历史深度Depth发布队列和订阅队列的缓存大小。需要结合Durability和Reliability来理解。截止时间Deadline发布者承诺发布消息的最大间隔订阅者监控这个间隔。如果超时可以触发回调函数进行处理用于系统健康监测。// C 示例创建一个使用特定QoS配置的发布者 #include “rclcpp/rclcpp.hpp” #include “std_msgs/msg/string.hpp” // 定义自定义QoS配置 auto custom_qos rclcpp::QoS(rclcpp::KeepLast(10)); // 历史深度10 custom_qos.reliability(RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT); // 尽力而为 custom_qos.durability(RMW_QOS_POLICY_DURABILITY_VOLATILE); // 非持久 // 创建发布者 rclcpp::Publisherstd_msgs::msg::String::SharedPtr publisher this-create_publisherstd_msgs::msg::String(“chatter”, custom_qos);注意事项QoS不匹配是ROS2通信失败的常见原因发布者和订阅者的QoS策略必须“兼容”才能建立连接。例如一个要求RELIABLE的订阅者无法接收到一个提供BEST_EFFORT的发布者的消息。在调试时如果发现节点间无法通信除了检查话题名称一定要用ros2 topic info --verbose topic_name命令仔细对比双方的QoS配置。4. 开发工具链与生态体验的演变日常开发中我们打交道最多的除了代码就是各种工具。ROS2的工具链在ROS1的基础上有继承也有大刀阔斧的改革。4.1 构建系统从Catkin到ColconROS1/CatkinCatkin是基于CMake的但加入了大量ROS特有的宏和约定。工作空间初始化、包的查找、依赖管理都通过catkin_make或catkin build命令完成。它简单易用但扩展性一般且对非CMake项目如纯Python包支持较弱。ROS2/ColconColcon不是一个构建工具而是一个构建工具聚合器。它本身不编译代码而是负责发现工作空间中的包然后调用每个包自己的构建系统如CMake, Python setuptools, ament_cmake等进行构建。这种设计带来了极大的灵活性。优点支持混合语言工作空间C、Python包可以放在一起构建构建过程更并行化输出更清晰与现代CI/CD流程集成更好。命令变化catkin_make-colcon buildsource devel/setup.bash-source install/setup.bash。注意ROS2的构建产物默认放在install目录下而非devel目录。4.2 命令行工具从rosxxx到ros2命令行工具的变化是最直观的。ROS2的命令更加统一和规范。功能ROS1 命令ROS2 命令关键变化点核心管理roscore无需ROS2无需单独启动核心节点相关rosnode listros2 node list命令统一为ros2 子命令格式rosrun pkg noderos2 run pkg node话题相关rostopic echo /topicros2 topic echo /topicrostopic pub ...ros2 topic pub ...pub命令格式略有不同服务相关rosservice call /srv ...ros2 service call /srv ...参数相关rosparam listros2 param listROS2参数支持动态类型和更丰富的操作包与执行roslaunch pkg launch.launchros2 launch pkg launch.py启动文件从XML变为Python关于启动文件Launch File的巨变这是迁移过程中学习成本最高的部分之一。ROS1使用XML格式的.launch文件虽然功能强大但编写复杂逻辑如条件判断、循环非常笨拙通常需要结合arg和include来实现。ROS2全面转向Python脚本.py作为启动文件。这带来了无与伦比的灵活性和强大功能。优势你可以使用完整的Python语法进行条件判断、循环、从文件读取配置、进行参数计算等。启动过程变得可编程。示例对比!-- ROS1 launch XML (片段) -- launch arg nameuse_sim defaultfalse/ group if$(arg use_sim) node namesim_node pkgsim_pkg typesim_node/ /group group unless$(arg use_sim) node namereal_node pkgreal_pkg typereal_node/ /group /launch# ROS2 launch Python (等效功能) from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, GroupAction from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def generate_launch_description(): use_sim_arg DeclareLaunchArgument(use_sim, default_valuefalse) # 使用Python逻辑决定启动哪个节点 real_group GroupAction(conditionIfCondition(PythonExpression([not , LaunchConfiguration(use_sim)])), actions[Node(packagereal_pkg, executablereal_node)]) sim_group GroupAction(conditionIfCondition(LaunchConfiguration(use_sim)), actions[Node(packagesim_pkg, executablesim_node)]) return LaunchDescription([ use_sim_arg, real_group, sim_group ])迁移经验虽然Python启动文件更强大但初期编写起来比XML更复杂。建议从复制修改官方示例开始逐步掌握LaunchDescription、Node、GroupAction、SetParameter等核心类的用法。社区也提供了ros2launch等工具可以辅助将简单的ROS1 launch文件转换为ROS2格式但复杂逻辑仍需手动重写。4.3 调试与可视化工具rqtROS1的rqt工具套件大部分已移植到ROS2如rqt_graph查看节点拓扑、rqt_console查看日志。使用体验基本一致。rviz2ROS2的rviz。核心功能稳定但部分ROS1时代的插件可能尚未移植。对于自定义显示类型需要按照ROS2的接口重新编写插件。rosbag2ROS2的录包工具。存储格式从.bag变为SQLite3数据库默认。这意味着你不能直接用rosbag play播放ROS2的包也不能用ros2 bag play播放ROS1的包。回放命令也变为ros2 bag play bag_folder。它的优势是支持并行读写、更好的数据索引和可扩展的存储后端。5. 从ROS1迁移到ROS2实战策略与避坑指南如果你有一个现有的ROS1项目考虑迁移到ROS2那么你需要一个系统性的策略而不是盲目地重写代码。5.1 迁移评估与路径规划首先问自己几个问题迁移的必要性有多大如果项目是长期维护的产品需要更高的可靠性、网络支持或实时性那么迁移收益大。如果只是短期研究原型且ROS1工具链完全满足迁移成本可能高于收益。项目规模与复杂度如何小型项目可以尝试整体重写。中大型项目建议采用渐进式迁移。依赖的第三方ROS1包是否有ROS2版本使用rosdep和vcs管理的依赖需要逐一检查其ROS2支持情况。很多核心包如navigation2,tf2已有ROS2版本但一些小众或陈旧的包可能没有。渐进式迁移策略推荐桥接共存使用ros1_bridge包。这是一个特殊的节点可以在同一台机器上同时运行ROS1和ROS2的roscore实际上是ROS2的DDS域并在两者之间转发指定话题、服务、参数的消息。这允许你将系统一部分一部分地迁移新旧模块暂时共存。优点风险低可以分模块验证。缺点桥接本身是一个性能瓶颈和潜在的单点故障且某些复杂的消息类型如包含动态数组的Action可能无法完美桥接。逐模块重写将系统划分为相对独立的模块如感知、定位、规划、控制。选择一个耦合度低、且其ROS2生态相对成熟的模块开始重写。重写时不仅仅是API的替换更要利用ROS2的新特性如生命周期、QoS进行优化设计。接口冻结与重构在重写模块时尽量保持其对外话题、服务、消息接口不变。这样可以最小化对其他模块的影响。同时这也是一个绝佳的代码重构机会清理技术债务。5.2 API变更与代码重写要点代码层面的迁移是体力活但有一些固定模式和常见“坑点”。头文件与命名空间// ROS1 #include “ros/ros.h” #include “std_msgs/String.h” ros::init(argc, argv, “node_name”); ros::NodeHandle nh; ros::Publisher pub nh.advertisestd_msgs::String(“topic”, 10); // ROS2 #include “rclcpp/rclcpp.hpp” #include “std_msgs/msg/string.hpp” // 注意子命名空间 msg rclcpp::init(argc, argv); auto node std::make_sharedrclcpp::Node(“node_name”); auto pub node-create_publisherstd_msgs::msg::String(“topic”, 10);消息定义ROS2的消息定义.msg,.srv,.action文件格式与ROS1基本兼容但生成的头文件位置和命名空间不同。find_package和ament_target_dependencies的用法需要对应修改CMakeLists.txt和package.xml。时间与定时器// ROS1 ros::Time now ros::Time::now(); ros::Duration(1.0).sleep(); ros::Timer timer nh.createTimer(ros::Duration(0.1), callback); // ROS2 rclcpp::Time now node-now(); rclcpp::sleep_for(std::chrono::seconds(1)); auto timer node-create_wall_timer(std::chrono::milliseconds(100), callback);参数处理ROS2的参数系统更强大支持动态类型声明时可不指定类型运行时推断和动态重配置通过服务或rqt_reconfigure。// ROS2 声明和获取参数 node-declare_parameter(“my_param”, “default_value”); // 动态类型 std::string param_value node-get_parameter(“my_param”).as_string();5.3 常见问题与排查技巧实录在迁移和开发过程中我记录了一些高频问题问题1节点启动了但ros2 topic list看不到话题或者订阅者收不到消息。排查思路检查话题名称确保发布和订阅的话题名称完全一致包括斜杠/。检查命名空间ROS2节点有完整的命名空间。使用ros2 node info node_name查看节点完整名称和其发布/订阅的话题。检查QoS匹配这是最容易被忽略的一点使用ros2 topic info --verbose topic_name分别查看发布者和订阅者的QoS配置。确保Reliability和Durability等策略是兼容的。一个要求RELIABLE的订阅者无法连接一个提供BEST_EFFORT的发布者。检查DDS实现不同的DDS实现如Fast DDS, Cyclone DDS默认配置可能不同有时会导致发现延迟或失败。可以尝试设置环境变量RMW_IMPLEMENTATIONrmw_fastrtps_cpp或RMW_IMPLEMENTATIONrmw_cyclonedds_cpp来指定。问题2ROS2的日志输出太杂乱如何有效管理技巧ROS2的日志级别更精细DEBUG, INFO, WARN, ERROR, FATAL。在代码中合理使用RCLCPP_DEBUG,RCLCPP_INFO等宏。在启动节点时可以通过参数设置日志级别ros2 run my_pkg my_node --ros-args --log-level DEBUG。也可以使用rqt_console工具来过滤和查看日志。问题3使用ros1_bridge时某些自定义消息类型无法桥接。解决方案ros1_bridge需要为每种需要桥接的消息类型生成转换代码。对于自定义消息你需要确保该消息包在ROS1和ROS2中同名并且消息定义完全一致。然后在编译ros1_bridge时在你的colcon工作空间中同时包含ROS1和ROS2版本的该消息包ros1_bridge的CMake脚本会自动尝试为它们生成桥接代码。如果自动生成失败你可能需要手动编写桥接函数这比较复杂通常意味着你的消息设计可能需要调整以兼容两者。问题4ROS2的执行器Executor和回调组Callback Group怎么用解析这是ROS2提升实时性和并发能力的关键。默认的SingleThreadedExecutor所有回调在一个线程中串行执行和ROS1类似。MultiThreadedExecutor则使用一个线程池。关键点你可以创建回调组ReentrantCallbackGroup 或 MutuallyExclusiveCallbackGroup并将定时器、订阅者、服务服务器的回调分配到不同的组。Reentrant组内的回调可以并行执行MutuallyExclusive组内的回调则串行执行。这让你可以精细控制哪些回调可以并行如处理两个独立的传感器哪些必须互斥如访问同一个硬件资源。// 示例创建互斥回调组避免两个回调同时访问共享资源 auto callback_group node-create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); auto sub_options rclcpp::SubscriptionOptions(); sub_options.callback_group callback_group; subscription_ node-create_subscription...(..., sub_options); // 将该回调组添加到执行器 executor.add_callback_group(callback_group, node-get_node_base_interface());对于大多数应用使用默认设置即可。只有当你在回调中执行耗时操作并担心阻塞其他关键回调时才需要深入研究回调组。从ROS1到ROS2远不止是API的更新更是一次开发思维和系统设计理念的升级。它要求开发者从“让程序跑起来”转向“让系统稳定、可靠、可维护地跑下去”。这个过程充满挑战但带来的在可靠性、可扩展性和部署灵活性上的收益是巨大的。我的建议是对于新项目如果没有历史包袱直接选择ROS2。对于存量项目则需仔细评估制定渐进式迁移计划并充分利用ros1_bridge等工具降低风险。最重要的是拥抱变化理解其背后的设计动机这样才能真正发挥出新框架的威力。
返回列表