ARTICLE DETAIL

资讯详情

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

从ROS 1到ROS 2实战:基于DDS的机器人仿真与双语言开发指南

从ROS 1到ROS 2实战:基于DDS的机器人仿真与双语言开发指南 1. 项目缘起为什么现在要从ROS 1转向ROS 2如果你和我一样是从ROS 1Robot Operating System 1时代过来的机器人开发者大概都经历过这样的场景写一个简单的发布/订阅节点启动roscore然后祈祷网络配置别出幺蛾子尤其是当你想在多台机器上分布式运行节点时那种被ROS_MASTER_URI和ROS_HOSTNAME支配的恐惧至今想起来都头皮发麻。ROS 1的通信架构基于TCPROS/UDPROS严重依赖一个中心化的Master节点它一旦挂了整个系统就瘫痪了这在追求高可靠性的机器人应用里是个致命伤。所以当ROS 2带着DDSData Distribution Service这个“大杀器”出现时我几乎是迫不及待地跳了进来。DDS是一种成熟的工业标准数据分发中间件它原生支持去中心化的发现机制、丰富的服务质量QoS策略以及真正的实时性。这意味着没有单点故障了节点可以动态加入和离开你可以精细控制数据的可靠性、持久化、截止时间等。更重要的是ROS 2从设计之初就考虑了产品化部署对Windows、macOS和实时操作系统如RTOS的支持也更友好。但转向ROS 2也有阵痛期。最明显的就是编程模型和API的变化以及“现代C”和“Python 3”成了官方唯二支持的“一等公民”语言。ROS 1里那些熟悉的ros::Publisher、rospy的写法在ROS 2里都有了新的面貌。这个项目就是想通过构建一个具体的、简单的机器人仿真项目手把手地带你跨越这个阵痛期。我们不搞复杂的SLAM或机械臂控制就从最基础的“小海龟”TurtleBot3仿真开始但会用现代C和Python 3两种方式把ROS 2的核心概念——节点、话题、服务、参数、动作——都实实在在地实现一遍。你会发现理解了这些基础再去啃导航、感知那些高级功能包会顺畅得多。2. 环境搭建避开那些“看似简单”的配置坑工欲善其事必先利其器。ROS 2的环境搭建是第一步也是最容易劝退新手的一步。网上教程很多但往往省略了关键细节导致你跟着做却跑不通。这里我结合最近的实践以Ubuntu 22.04和ROS 2 Humble Hawksbill为例把核心步骤和避坑点捋清楚。2.1 系统与ROS 2发行版选择首先强烈建议使用Ubuntu Linux作为开发环境。虽然ROS 2支持Windows和macOS但社区生态、工具链和文档最完善的依然是Ubuntu。目前ROS 2的长期支持LTS版本是Humble Hawksbill对应Ubuntu 22.04 Jammy Jellyfish和最新的Rolling Ridley。对于学习和生产我推荐使用Humble因为它足够稳定且有长达5年的支持周期。安装ROS 2本身官方文档的步骤是权威的。但有几个细节必须注意区域设置Locale在安装前务必确保你的系统locale是UTF-8。运行locale检查如果LANG不是en_US.UTF-8或类似需要先设置。否则在后续编译或运行Python节点时可能会遇到奇怪的字符编码错误。sudo apt update sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALLen_US.UTF-8 LANGen_US.UTF-8 export LANGen_US.UTF-8软件源配置添加ROS 2 apt仓库后一定要导入GPG密钥。有时网络问题会导致密钥导入失败进而无法更新软件包列表。如果遇到The following signatures couldn‘t be verified这类错误可以尝试更换软件源镜像或者手动从浏览器下载密钥文件进行导入。安装完整版建议安装ros-humble-desktop它包含了ROS、RViz、Gazebo等图形化工具和基础功能包。对于机器人仿真项目这是必需品。2.2 现代C与Python 3开发环境配置ROS 2 Humble默认要求C17和Python 3.10。这意味着我们的代码风格需要与时俱进。对于C开发编译器确保GCC版本至少为11gcc --version。Ubuntu 22.04默认是11没问题。构建工具ROS 2使用Colcon作为构建工具它替代了ROS 1的catkin_make。你需要安装它sudo apt install python3-colcon-common-extensions。代码编辑器/IDEVSCode是绝佳选择。你需要安装以下插件C/C(Microsoft)提供智能感知、调试、代码导航。CMake Tools因为ROS 2的包使用CMake作为构建系统这个插件能帮你轻松配置、构建和调试。ROS(Microsoft)专门为ROS/ROS 2开发的插件提供Launch文件支持、话题查看、服务调用等集成功能。Python用于Python节点的开发。这里有个关键配置为了让VSCode的C插件能正确索引ROS 2的头文件你需要在项目根目录的.vscode/c_cpp_properties.json文件中正确设置includePath和compilerPath。一个常见的错误是只包含了系统路径没包含你的ROS 2工作空间install目录下的头文件路径导致代码提示和跳转失效。对于Python开发虚拟环境强烈建议为每个ROS 2项目创建独立的Python虚拟环境。虽然ROS 2的节点可以直接用系统Python运行但使用虚拟环境可以完美隔离依赖避免不同项目间的包版本冲突。使用venv模块创建cd ~/your_ros2_ws/src python3 -m venv .venv source .venv/bin/activate激活虚拟环境后再安装项目特定的Python包。注意ROS 2的核心包如rclpy是系统级安装的虚拟环境通过pip安装的包是其补充。依赖管理在Python包的setup.py或setup.cfg中明确定义install_requires。对于ROS 2 Python包通常至少需要setuptools和ament_python。2.3 工作空间结构与第一个“Hello World”ROS 2的工作空间概念和ROS 1类似但结构更清晰。我们创建一个典型的工作空间mkdir -p ~/ros2_ws/src cd ~/ros2_ws接下来我们分别用C和Python创建一个最简单的发布者节点来验证环境。C版本在src下创建一个包ros2 pkg create --build-type ament_cmake --node-name talker cpp_hello_world。这行命令一次性完成了包创建和节点文件生成。打开src/talker.cpp你会看到一个简单的发布者。编译它cd ~/ros2_ws colcon build --packages-select cpp_hello_world source install/setup.bash ros2 run cpp_hello_world talker如果终端开始周期性地打印“Hello World”消息恭喜你C环境通了。Python版本创建Python包ros2 pkg create --build-type ament_python --node-name talker py_hello_world。同样查看py_hello_world/py_hello_world/talker.py。编译对于Python包colcon主要是进行安装和配置和运行cd ~/ros2_ws colcon build --packages-select py_hello_world source install/setup.bash ros2 run py_hello_world talker注意每次新开终端或者编译了新包都必须先source install/setup.bash这个操作会将你工作空间下的包添加到ROS 2的环境变量中。你可以把这行命令加到~/.bashrc里但我不建议这么做特别是当你同时有多个工作空间时容易造成污染。更好的习惯是进入哪个工作空间就source哪个。3. 核心概念实战用双语言驱动一个仿真机器人环境跑通后我们进入正题实现一个能控制仿真机器人移动的简单项目。我们选择Gazebo经典Gazebo Classic和TurtleBot3仿真模型因为它轻量且社区支持好。这个项目将涵盖ROS 2五大核心通信机制中的三个话题发布/订阅、服务请求/响应和参数。3.1 项目目标与仿真环境搭建我们的目标是创建一个ROS 2包它包含两个节点。键盘控制节点监听键盘输入WASD将移动指令线速度、角速度通过话题发布出去。机器人运动节点订阅上述速度指令话题并转换成TurtleBot3仿真模型能理解的/cmd_vel话题消息类型为geometry_msgs/msg/Twist驱动机器人运动。同时我们将实现一个简单的服务用于动态修改机器人运动的最大速度参数。首先安装仿真环境sudo apt install ros-humble-gazebo-ros-pkgs ros-humble-turtlebot3-gazebo安装后设置一个环境变量指定使用哪款TurtleBot3模型例如Burgerecho ‘export TURTLEBOT3_MODELburger‘ ~/.bashrc source ~/.bashrc现在可以启动一个空世界的Gazebo仿真并加载TurtleBot3ros2 launch turtlebot3_gazebo empty_world.launch.py如果一切顺利Gazebo客户端会打开里面有一个小小的TurtleBot3 Burger机器人。3.2 C实现面向对象与现代特性我们创建一个C包cpp_turtle_controller。现代CC17在ROS 2开发中能带来更安全、更高效的代码。这里重点讲几个关键实践。节点类的封装ROS 2的C API鼓励使用面向对象的方式。我们将机器人运动节点封装成一个类。// robot_mover.hpp #include “rclcpp/rclcpp.hpp“ #include “geometry_msgs/msg/twist.hpp“ #include “std_msgs/msg/float64_multi_array.hpp“ // 用于接收自定义速度指令 class RobotMover : public rclcpp::Node { public: RobotMover(); private: void velocityCallback(const std_msgs::msg::Float64MultiArray::SharedPtr msg); rclcpp::Subscriptionstd_msgs::msg::Float64MultiArray::SharedPtr subscription_; rclcpp::Publishergeometry_msgs::msg::Twist::SharedPtr publisher_; rclcpp::TimerBase::SharedPtr timer_; double max_linear_speed_; double max_angular_speed_; // 使用std::atomic保证多线程下参数安全 std::atomicdouble current_linear_; std::atomicdouble current_angular_; };智能指针与生命周期管理注意SharedPtr的使用。ROS 2的订阅回调函数参数、Publisher和Subscription对象都使用std::shared_ptr进行管理这避免了ROS 1时代手动管理指针内存的麻烦利用RAII资源获取即初始化机制自动清理资源。QoS策略配置这是ROS 2相比ROS 1的重大进步。在创建Publisher或Subscription时我们可以指定QoS。例如对于速度指令我们可能希望它是最新的即使丢失一些旧消息也没关系这适合用Volatile的durability持久性和BestEffort的reliability可靠性。而对于地图数据我们则需要Reliable和TransientLocal的持久性以便新加入的节点也能收到之前发布的地图。auto qos rclcpp::QoS(rclcpp::KeepLast(10)).reliability(RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT); publisher_ this-create_publishergeometry_msgs::msg::Twist(“/cmd_vel“, qos);参数声明与回调我们声明max_linear_speed和max_angular_speed两个参数并允许通过ROS 2服务动态修改。在构造函数中this-declare_parameter(“max_linear_speed“, 0.22); // TurtleBot3 Burger最大线速度 this-declare_parameter(“max_angular_speed“, 2.84); // 最大角速度 // 获取参数值 max_linear_speed_ this-get_parameter(“max_linear_speed“).as_double();然后我们可以创建一个服务当被调用时更新这两个参数并触发一个参数变更回调函数。CMakeLists.txt关键配置确保你的CMakeLists.txt正确链接了依赖并使用了C17标准。add_executable(robot_mover src/robot_mover.cpp) ament_target_dependencies(robot_mover rclcpp geometry_msgs std_msgs) ... set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)3.3 Python实现简洁与异步的魅力再创建一个Python包py_turtle_controller。Python的代码通常更简洁特别是在处理回调和服务时。节点与回调Python版节点定义非常直观。# robot_mover_node.py import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from std_msgs.msg import Float64MultiArray class RobotMoverNode(Node): def __init__(self): super().__init__(‘robot_mover‘) self.subscription self.create_subscription( Float64MultiArray, ‘velocity_commands‘, self.velocity_callback, 10 # 队列深度 ) self.publisher self.create_publisher(Twist, ‘/cmd_vel‘, 10) self.declare_parameter(‘max_linear_speed‘, 0.22) self.declare_parameter(‘max_angular_speed‘, 2.84) self.max_linear self.get_parameter(‘max_linear_speed‘).value self.max_angular self.get_parameter(‘max_angular_speed‘).value self.current_linear 0.0 self.current_angular 0.0 def velocity_callback(self, msg): # 处理速度指令 if len(msg.data) 2: self.current_linear max(min(msg.data[0], self.max_linear), -self.max_linear) self.current_angular max(min(msg.data[1], self.max_angular), -self.max_angular) twist_msg Twist() twist_msg.linear.x float(self.current_linear) twist_msg.angular.z float(self.current_angular) self.publisher.publish(twist_msg) self.get_logger().info(f‘Publishing: linear{self.current_linear:.2f}, angular{self.current_angular:.2f}‘)异步与服务Python的asyncio库与ROS 2的异步特性结合得很好。例如创建一个修改参数的服务from example_interfaces.srv import SetParameters from rclpy.callback_groups import ReentrantCallbackGroup from rclpy.qos import QoSProfile, ReliabilityPolicy class ParameterServiceNode(Node): def __init__(self): super().__init__(‘parameter_service‘) # 使用ReentrantCallbackGroup允许服务回调被并发执行 cb_group ReentrantCallbackGroup() self.srv self.create_service(SetParameters, ‘set_max_speed‘, self.set_max_speed_callback, callback_groupcb_group) async def set_max_speed_callback(self, request, response): # 异步处理服务请求 self.get_logger().info(f‘Incoming request: linear{request.linear}, angular{request.angular}‘) # ... 参数验证与更新逻辑 ... response.success True response.message “Parameters updated successfully“ return response使用async和ReentrantCallbackGroup可以避免服务处理阻塞主线程这在需要同时处理多个服务请求或话题消息时非常有用。setup.py与entry pointsPython包需要在setup.py中正确注册节点入口点这样ros2 run命令才能找到它。entry_points{ ‘console_scripts‘: [ ‘robot_mover py_turtle_controller.robot_mover_node:main‘, ‘keyboard_teleop py_turtle_controller.keyboard_teleop_node:main‘, ], },3.4 键盘控制节点的实现细节无论是C还是Python键盘控制节点的核心都是非阻塞地读取标准输入。我们不能用std::cin或input()因为它们会阻塞节点的主线程。解决方案是使用线程或异步I/O。在C中可以创建一个独立的线程来循环读取键盘输入例如使用ncurses库或简单的termios设置原始模式。在Python中可以使用select模块或pynput第三方库来非阻塞监听键盘事件。这里以Python的pynput为例需先pip install pynputfrom pynput import keyboard import threading class KeyboardTeleopNode(Node): def __init__(self): super().__init__(‘keyboard_teleop‘) self.publisher self.create_publisher(Float64MultiArray, ‘velocity_commands‘, 10) self.speed_linear 0.0 self.speed_angular 0.0 self.increment 0.05 # 启动键盘监听线程 listener keyboard.Listener(on_pressself.on_press, on_releaseself.on_release) listener.start() def on_press(self, key): try: if key.char ‘w‘: self.speed_linear self.increment elif key.char ‘s‘: self.speed_linear - self.increment elif key.char ‘a‘: self.speed_angular self.increment elif key.char ‘d‘: self.speed_angular - self.increment elif key.char ‘ ‘: # 空格键停止 self.speed_linear 0.0 self.speed_angular 0.0 self.publish_velocity() except AttributeError: pass def publish_velocity(self): msg Float64MultiArray() msg.data [self.speed_linear, self.speed_angular] self.publisher.publish(msg)4. 构建、运行与深度调试实战代码写完了真正的挑战才刚刚开始如何把它们组织起来并顺利运行和调试4.1 使用Colcon构建多语言混合工作空间我们的工作空间里现在有C包和Python包。Colcon可以很好地处理这种混合情况。在工作空间根目录直接运行colcon build即可。但有几个高级技巧选择性构建--packages-select PACKAGE_NAME只构建指定的包节省时间。构建后自动生效使用--symlink-install选项。这会在install目录中创建符号链接指向build目录下的库和可执行文件。这样当你修改Python脚本或某些配置文件后无需重新colcon build直接运行就生效了。但注意对于C代码修改后仍需重新构建。并行构建--parallel-workers NUMBER可以指定并行编译的任务数加快构建速度。处理依赖如果遇到“找不到包”的错误首先检查是否已通过apt或rosdep安装了所有依赖。在工作空间根目录运行rosdep install --from-paths src --ignore-src -r -y可以自动安装缺失的系统依赖。构建成功后install目录下会有每个包的独立文件夹里面包含了可执行文件、Python模块、头文件和共享库。4.2 Launch文件一键启动复杂系统手动开多个终端运行每个节点是低效的。ROS 2的Launch文件Python编写可以帮我们一键启动整个系统。创建一个launch/目录编写turtlebot3_teleop.launch.pyfrom launch import LaunchDescription from launch_ros.actions import Node from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable from launch.substitutions import LaunchConfiguration def generate_launch_description(): # 设置环境变量指定TurtleBot3模型 set_tb3_model SetEnvironmentVariable(‘TURTLEBOT3_MODEL‘, ‘burger‘) # 声明一个启动参数用于Gazebo的世界文件 world_file_arg DeclareLaunchArgument( ‘world_file‘, default_value‘‘, # 默认空世界 description‘Path to Gazebo world file‘ ) # 启动Gazebo仿真使用现有的turtlebot3_gazebo包中的launch文件 # 这里我们直接调用已有的launch文件更规范 gazebo_launch IncludeLaunchDescription( PythonLaunchDescriptionSource([ get_package_share_directory(‘turtlebot3_gazebo‘), ‘/launch/empty_world.launch.py‘ ]), launch_arguments{‘world‘: LaunchConfiguration(‘world_file‘)}.items() ) # 启动我们的C机器人运动节点 robot_mover_cpp Node( package‘cpp_turtle_controller‘, executable‘robot_mover‘, name‘robot_mover_cpp‘, output‘screen‘, # 将日志输出到屏幕 parameters[{‘max_linear_speed‘: 0.22, ‘max_angular_speed‘: 2.84}] ) # 启动我们的Python键盘控制节点 keyboard_teleop Node( package‘py_turtle_controller‘, executable‘keyboard_teleop‘, name‘keyboard_teleop_py‘, output‘screen‘ ) return LaunchDescription([ set_tb3_model, world_file_arg, gazebo_launch, robot_mover_cpp, keyboard_teleop, ])然后通过ros2 launch cpp_turtle_controller turtlebot3_teleop.launch.py即可启动整个仿真系统。Launch文件的能力远不止于此它还能组合其他launch文件、设置命名空间、重映射话题名、条件执行等是管理复杂机器人系统的利器。4.3 调试从日志、命令行到可视化工具程序跑起来了但机器人不动或者动得不正常怎么办1. 日志系统ROS 2内置了分级的日志系统DEBUG, INFO, WARN, ERROR, FATAL。在代码中合理使用RCLCPP_INFO(this-get_logger(), “...“)或self.get_logger().info(“...“)输出关键状态。可以通过ros2 run package_name node_name --ros-args --log-level DEBUG来动态调整节点的日志级别在调试时看到更详细的信息。2. 命令行工具链这是ROS 2调试的瑞士军刀。ros2 node list查看所有运行中的节点。ros2 topic list/ros2 topic echo /topic_name查看话题列表和实时消息。ros2 service list/ros2 service call /service_name service_type args查看和调用服务。ros2 param list/ros2 param get /node_name param_name查看和获取参数。ros2 bag record -o my_bag /topic1 /topic2录制话题数据包用于离线分析和复现问题。ros2 run rqt_graph rqt_graph启动节点图可视化工具直观看到节点、话题、服务之间的连接关系是诊断通信问题的最佳工具。如果keyboard_teleop节点和robot_mover节点之间的velocity_commands话题没有连线说明订阅关系没建立可能是话题名拼写错误或QoS不匹配。3. 使用GDB/LLDB调试C节点对于段错误Segmentation Fault或逻辑错误需要调试器。在Launch文件中配置节点时可以指定启动调试器Node( package..., executable..., name..., output‘screen‘, prefix‘xterm -e gdb --args‘, # 在xterm中启动gdb # 或者使用 ‘lldb --‘ for LLDB )更常见的是在VSCode中配置CMake Tools插件进行调试。在.vscode/launch.json中配置调试目标为编译出的可执行文件可以设置断点、单步执行、查看变量。4. RViz2可视化虽然我们这个简单项目没用到传感器数据但RViz2是机器人调试的“眼睛”。你可以添加一个“RobotModel”显示类型并订阅/tf话题就能看到机器人模型的运动状态。对于更复杂的项目可以可视化激光雷达点云、摄像头图像、路径规划结果等。4.4 常见问题与避坑指南“Package ‘xxx‘ not found“ 或 “Could not find a package configuration file“原因最常见的原因是忘记source install/setup.bash或者在不同终端中source了不同的工作空间。解决确保在运行节点的终端里已经source了当前工作空间的setup.bash。用echo $ROS_PACKAGE_PATH检查包路径是否包含你的工作空间。节点启动后立即退出无错误信息原因可能是节点代码中的rclcpp::spin或rclpy.spin没有被正确调用或者主函数提前返回了。在Python中如果节点对象在try...except块外被创建且没有调用spin脚本执行完就会退出。解决C确保有rclcpp::spin(node)Python确保有rclpy.spin(node)并且放在try...except KeyboardInterrupt:块内。检查日志输出有时错误被默认的日志级别过滤了尝试用--ros-args --log-level DEBUG启动节点。话题无法通信rqt_graph显示节点孤立原因A话题名或类型不匹配。Publisher和Subscriber的话题名必须完全一致包括命名空间。消息类型也必须完全一致std_msgs/msg/String和std_msgs/msg/Int32就是不同的类型。解决用ros2 topic info /your_topic_name和ros2 interface show message_type仔细核对。原因BQoS策略不兼容。这是ROS 2特有的问题。例如Publisher的reliability是BestEffort而Subscriber要求Reliable它们就无法建立连接。解决检查创建Publisher和Subscription时的QoS配置确保它们兼容。通常使用默认的QoSrmw_qos_profile_sensor_data或rmw_qos_profile_system_default可以避免大部分问题但在跨网络或对实时性有要求时需特别注意。Gazebo模型加载失败黑屏或找不到模型原因环境变量TURTLEBOT3_MODEL没有设置或者Gazebo模型路径没有正确配置。解决确认已export TURTLEBOT3_MODELburger或waffle,waffle_pi。首次启动Gazebo时它会从网络下载模型如果网络慢会卡住。可以提前下载模型到本地~/.gazebo/models/目录。Python节点报“ModuleNotFoundError: No module named ‘xxx‘”原因Python的包路径问题。你的Python节点可能找不到自己包内的模块或者找不到依赖的第三方库。解决对于ROS 2 Python包确保在setup.py中正确配置了package_dir和packages。对于第三方库如果是在虚拟环境中确保已pip install。一个常见的坑是在Launch文件中启动Python节点时其运行环境可能没有激活虚拟环境。一种方法是在节点的Python脚本开头通过sys.path手动添加路径但这不优雅。更好的做法是确保你的ROS 2工作空间在激活了虚拟环境的环境下被source和构建。
返回列表