【机器人开发四】从零开始创建一个ROS2机器人工程(1):创建一个ROS2小车,掌握ROS2工程以及消息订阅机制

【机器人开发四】从零开始创建一个ROS2机器人工程(1):创建一个ROS2小车,掌握ROS2工程以及消息订阅机制

【机器人开发四】从零开始创建一个ROS2机器人工程(1):熟悉ROS2工程目录,做一个turtle3小车

本文将手把手教你从零开始搭建一个能用键盘控制移动的两轮机器人。基于 ROS 2 Jazzy + Gazebo Harmonic 环境。

本文的目的

  1. 深入理解一个标准的ROS2工程的目录结构
  2. 理解ROS2工程中最核心的文件
  3. 掌握ROS2项目的启动方法
  4. 创建一个turtle3机器人,熟悉话题的发布订阅机制

文章目录

1. 创建工作空间和功能包

一个标准的 ROS 工程源码目录应有如下结构。ros2_ws 是项目目录,src 子目录是所有源码的目录。

# 创建工作空间mkdir-p ~/ros2_ws/src cd ~/ros2_ws/src # 创建 ROS 2 功能包(使用 ament_cmake 类型,支持 Gazebo 插件) ros2 pkg create --build-type ament_cmake my_robot_description # 进入包目录并创建标准子目录cd my_robot_description mkdir urdf launch worlds config meshes models 

执行完后,目录结构如下:

ros2_ws/ # 工作空间根目录 └── src/ # 源代码目录(所有功能包放这里) └── my_robot_description/ # 机器人描述功能包目录 ├── CMakeLists.txt # 编译脚本(自动生成) ├── package.xml # 包描述文件(自动生成) ├── src/ # C++源码目录,用于机器人控制编程(自动生成) ├── include/ # C++头文件目录(自动生成) ├── urdf/ # 机器人 URDF/Xacro 文件(核心) ├── launch/ # Launch 启动文件(核心) ├── worlds/ # Gazebo 世界场景文件(可选) ├── config/ # RViz/Gazebo 配置文件(可选) ├── meshes/ # 3D 网格模型文件(可选) └── models/ # Gazebo 模型定义(可选) 
提示:标注为"核心"的文件和目录是必须创建的,机器人缺少它们无法工作标注为"自动生成"的文件由 ros2 pkg create 命令自动生成,一般不需要手动修改标注为"可选"的目录可以根据需要创建,不是完成本教程的必要条件
提示:标准 ROS 2 工程的目录结构让代码易于维护,colcon build 能正确识别所有资源。

注意:功能包必须放在 src/ 目录下。如果放错位置(如直接在 ros2_ws/ 下创建),colcon build 将无法识别。


2. 编写 Launch 文件

~/ros2_ws/src/my_robot_description/launch/ 目录下创建 rsp.launch.py

import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node import xacro defgenerate_launch_description(): pkg_name ='my_robot_description'# 声明 use_sim_time 参数(仿真工程的标配) declare_use_sim_time = DeclareLaunchArgument('use_sim_time', default_value='false', description='Use simulation (Gazebo) clock if true')# 获取参数值 use_sim_time = LaunchConfiguration('use_sim_time')# 解析 Xacro 文件 xacro_file = os.path.join(get_package_share_directory(pkg_name),'urdf','robot.urdf.xacro') robot_description_config = xacro.process_file(xacro_file).toxml()# 配置 robot_state_publisher 节点 node_robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', output='screen', parameters=[{'robot_description': robot_description_config,'use_sim_time': use_sim_time }])return LaunchDescription([ declare_use_sim_time, node_robot_state_publisher ])
提示get_package_share_directory() 会自动找到包的安装路径,避免硬编码路径。

注意:路径不要写死(如 /home/kason/...),否则换一台电脑或换一个用户名就会失效。


3. 配置 CMakeLists.txt

编辑 ~/ros2_ws/src/my_robot_description/CMakeLists.txt,在末尾添加:

# 安装 launch, urdf, worlds 等目录到 share 路径下 install(DIRECTORY launch urdf worlds config DESTINATION share/${PROJECT_NAME} ) 

然后编译:

cd ~/ros2_ws colcon build --symlink-install 

看到以下输出说明编译成功:

Starting >>> my_robot_description Finished <<< my_robot_description 

如果使用 --symlink-install,以后修改 launch 文件或 urdf 文件后,直接运行即可生效,不需要重新编译。

刷新环境:

source install/setup.bash 

验证 launch 文件是否被正确安装:

ros2 launch my_robot_description rsp.launch.py --show-args 

成功时会显示:

Arguments: 'use_sim_time': Use simulation (Gazebo) clock if true (default: 'false') 

注意:如果忘记修改 CMakeLists.txt,直接编译后运行会报错 file 'rsp.launch.py' was not found in the share directory。这是因为 ros2 launch 只会去 install/ 目录查找文件,不会去 src/。


4. 编写机器人 URDF 模型

~/ros2_ws/src/my_robot_description/urdf/ 目录下创建 robot.urdf.xacro

<?xml version="1.0"?><robotxmlns:xacro="http://www.ros.org/wiki/xacro"name="my_robot"><materialname="white"><colorrgba="1 1 1 1"/></material><!-- base_link 是机器人投影到地面的中心点,所有传感器和轮子都相对于它偏移 --><linkname="base_link"></link><jointname="chassis_joint"type="fixed"><parentlink="base_link"/><childlink="chassis"/><originxyz="-0.1 0 0"/></joint><linkname="chassis"><visual><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry><materialname="white"/></visual></link></robot>

注意:机器人模型必须以 base_link 作为根节点。Nav2 导航算法需要以它为旋转中心,直接以 chassis 开头会导致后续导航功能无法使用。


5. 在 RViz2 中可视化机器人

运行 Launch 文件:

ros2 launch my_robot_description rsp.launch.py 

成功启动后你会看到类似输出:

[INFO] [launch]: All log files can be found below /home/user/.ros/log/... [INFO] [launch]: Default logging verbosity is set to INFO [INFO] [robot_state_publisher-1]: process started with pid [1234] [robot_state_publisher-1] [INFO] [1774688498.607462389] [robot_state_publisher]: Robot initialized 

关键信号是看到 Robot initialized 这行字,说明机器人模型已被成功发布。

新开一个终端,打开 RViz2:

rviz2 

配置步骤:

  1. Fixed Frame:将 map 改为 base_link
  2. Add → 选择 RobotModel
  3. Add → 选择 TF

配置正确后,你将在 RViz 中央看到一个白色方块(chassis)。

图1 Fixed Frame,Add,RobotModel的位置:

请添加图片描述

图2 TF的位置:

请添加图片描述


图3 Description的位置,显示的白色方块

请添加图片描述
提示:如果看不到机器人,检查 RobotModel 下的 Description Topic 是否为 /robot_description

如果看到 TF 坐标轴但看不到白色方块,说明 RViz2 默认不知道去哪个话题找机器人描述,需要在 RobotModel 配置中手动选择 /robot_description


6. 添加轮子

修改 robot.urdf.xacro,在 </robot> 前添加轮子定义:

<materialname="blue"><colorrgba="0.2 0.2 1 1"/></material><!-- 左轮 --><jointname="left_wheel_joint"type="continuous"><parentlink="base_link"/><childlink="left_wheel"/><originxyz="0 0.175 0"rpy="-${pi/2} 0 0"/><axisxyz="0 0 1"/></joint><linkname="left_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual></link><!-- 右轮 --><jointname="right_wheel_joint"type="continuous"><parentlink="base_link"/><childlink="right_wheel"/><originxyz="0 -0.175 0"rpy="-${pi/2} 0 0"/><axisxyz="0 0 1"/></joint><linkname="right_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual></link>
提示continuous 类型的关节可以无限旋转,适合用于动力轮。

重新运行 ros2 launch my_robot_description rsp.launch.py,RViz 中将显示白色底盘和两个蓝色轮子。

如果 RViz2 显示如下错误:

Status: Error - right_wheel: No transform from [right_wheel] to [base_link] - left_wheel: No transform from [left_wheel] to [base_link] 

这是因为 robot_state_publisher 知道轮子存在,但不知道轮子的实时角度。需要在 rsp.launch.py 的 generate_launch_description() 函数中,在 return LaunchDescription([ 之前添加 joint_state_publisher_gui 节点:

defgenerate_launch_description():# ... 前面的代码保持不变 ... node_joint_state_publisher_gui = Node( package='joint_state_publisher_gui', executable='joint_state_publisher_gui', output='screen')return LaunchDescription([ declare_use_sim_time, node_robot_state_publisher, node_joint_state_publisher_gui # <-- 新增])

如果报错找不到包,运行:

sudoaptinstall ros-jazzy-joint-state-publisher-gui 

添加后重新运行,将弹出一个带滑块的小窗口,拖动滑块可以看到轮子随之转动,RViz 中的错误也会消失。

请添加图片描述

7. 添加物理属性

为了让机器人在 Gazebo 中不"穿墙"或"飞天",需要添加 <collision><inertial> 标签。

定义惯性计算宏

修改 robot.urdf.xacro,将下列代码添加到文件的开头,注意要在 <robot> 标签内、第一个 <link> 定义之前添加:

<!-- 长方体惯性宏 --><xacro:macroname="inertial_box"params="mass x y z *origin"><inertial><xacro:insert_blockname="origin"/><massvalue="${mass}"/><inertiaixx="${(1/12) * mass * (y*y+z*z)}"ixy="0.0"ixz="0.0"iyy="${(1/12) * mass * (x*x+z*z)}"iyz="0.0"izz="${(1/12) * mass * (x*x+y*y)}"/></inertial></xacro:macro><!-- 圆柱体惯性宏(轮子用) --><xacro:macroname="inertial_cylinder"params="mass radius length *origin"><inertial><xacro:insert_blockname="origin"/><massvalue="${mass}"/><inertiaixx="${(1/12) * mass * (3*radius*radius + length*length)}"ixy="0.0"ixz="0.0"iyy="${(1/12) * mass * (3*radius*radius + length*length)}"iyz="0.0"izz="${(1/2) * mass * (radius*radius)}"/></inertial></xacro:macro>

为底盘添加物理属性

<linkname="chassis"><visual><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry><materialname="white"/></visual><collision><originxyz="0.15 0 0.075"/><geometry><boxsize="0.3 0.3 0.15"/></geometry></collision><xacro:inertial_boxmass="0.5"x="0.3"y="0.3"z="0.15"><originxyz="0.15 0 0.075"rpy="0 0 0"/></xacro:inertial_box></link>

为轮子添加物理属性

<linkname="left_wheel"><visual><geometry><cylinderradius="0.05"length="0.04"/></geometry><materialname="blue"/></visual><collision><geometry><cylinderradius="0.05"length="0.04"/></geometry></collision><xacro:inertial_cylindermass="0.1"radius="0.05"length="0.04"><originxyz="0 0 0"rpy="0 0 0"/></xacro:inertial_cylinder></link>

同样为 right_wheel 添加 collision 和 inertial。

注意:如果 link 中缺少 collision,轮子在 Gazebo 中会穿过地面;如果缺少 inertial,Gazebo 无法计算物理,机器人会"飘走"或"坠落"。


8. 创建 Gazebo 仿真环境

安装 ros_gz 桥接包

sudoapt update sudoaptinstall ros-jazzy-ros-gz 

创建 sim.launch.py

~/ros2_ws/src/my_robot_description/launch/ 目录下创建:

import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node defgenerate_launch_description(): pkg_name ='my_robot_description'# 1. 启动 robot_state_publisher rsp = IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join( get_package_share_directory(pkg_name),'launch','rsp.launch.py')]), launch_arguments={'use_sim_time':'true'}.items())# 2. 启动 Gazebo Sim gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join( get_package_share_directory('ros_gz_sim'),'launch','gz_sim.launch.py')]), launch_arguments={'gz_args':'-r empty.sdf'}.items())# 3. 将机器人模型放入 Gazebo spawn_entity = Node( package='ros_gz_sim', executable='create', arguments=['-topic','robot_description','-name','my_cool_robot'], output='screen')return LaunchDescription([ rsp, gazebo, spawn_entity,])

注意:现在使用了gazebo以后,sim.launch.py 中不要包含 joint_state_publisher_gui 节点,否则会和 Gazebo 的物理引擎产生冲突,会导致电脑崩溃。

编译并运行

注意这次增加了新的文件,因此需要再次使用colcon进行项目构建。并且用须要再次使用source命令,让ubuntu找到launch文件。

cd ~/ros2_ws colcon build --symlink-install source install/setup.bash ros2 launch my_robot_description sim.launch.py 

成功启动后,你会看到 Gazebo 窗口弹出,里面有一个网格地面,以及你的机器人(白色底盘加两个蓝色轮子)出现在世界中央。


9. 添加万向轮

为了让机器人站稳(在两轮之外增加支撑点),修改 robot.urdf.xacro,在 </robot> 前添加:

<!-- 万向轮:gazebo reference 用于设置摩擦系数 --><jointname="caster_wheel_joint"type="fixed"><parentlink="chassis"/><childlink="caster_wheel"/><originxyz="0.24 0 0"/></joint><linkname="caster_wheel"><visual><geometry><sphereradius="0.05"/></geometry><materialname="black"/></visual><collision><geometry><sphereradius="0.05"/></geometry></collision><inertial><originxyz="0 0 0"rpy="0 0 0"/><massvalue="0.1"/><inertiaixx="0.0001"ixy="0.0"ixz="0.0"iyy="0.0001"iyz="0.0"izz="0.0001"/></inertial></link><gazeboreference="caster_wheel"><mu1value="0.001"/><mu2value="0.001"/></gazebo>
提示:万向轮需要极低的摩擦系数(mu1, mu2 接近 0),否则机器人转弯时会像被粘住。

重新运行 sim.launch.py,机器人将端端正正地站在地面上,不再向前或向后倾倒。

注意:动力轮的摩擦系数必须设置得比较高(mu1, mu2 > 1.0),如果也设成 0.001,轮子会原地打滑无法前进。


10. 添加差速驱动插件

robot.urdf.xacro</robot> 前添加:

<gazebo><pluginfilename="gz-sim-diff-drive-system"name="gz::sim::systems::DiffDrive"><left_joint>left_wheel_joint</left_joint><right_joint>right_wheel_joint</right_joint><wheel_separation>0.35</wheel_separation><wheel_radius>0.05</wheel_radius><max_wheel_torque>20</max_wheel_torque><max_wheel_acceleration>1.0</max_wheel_acceleration><topic>cmd_vel</topic><odom_topic>odom</odom_topic><frame_id>odom</frame_id><child_frame_id>base_link</child_frame_id><publish_odom>true</publish_odom><publish_odom_tf>true</publish_odom_tf><publish_wheel_tf>true</publish_wheel_tf></plugin><pluginfilename="gz-sim-joint-state-publisher-system"name="gz::sim::systems::JointStatePublisher"></plugin></gazebo>

重新运行 sim.launch.py,机器人依然出现在 Gazebo 中,只是现在它已经准备好接收控制指令了。


11. 键盘控制机器人

建立 ROS 2 与 Gazebo 的通信桥接

新开一个终端:

ros2 run ros_gz_bridge parameter_bridge /cmd_vel@geometry_msgs/msg/[email protected] 

成功运行后会显示类似:

[INFO] [gz_ros_bridge]: Creating GzROS2Bridge: topic /cmd_vel (ros -> gz)" 

启动键盘控制

再开一个新终端:

ros2 run teleop_twist_keyboard teleop_twist_keyboard 

成功后终端显示操作指南:

Reading from the keyboard and publishing to Twist! --------------------------- Moving around: u i o j k l m , . anything else : stop q/z : increase/decrease max speeds by 10% w/x : increase/decrease only linear speed by 10% e/c : increase/decrease only angular speed by 10% Currently: speed 0.5 turn 1.0 

现在点击这个终端使其激活,按 i 键让机器人前进,按 l 键向右转,按 ,(逗号)后退。观察 Gazebo 中的机器人是否随之移动。

请添加图片描述

避坑总结

错误现象原因解决方案
file not foundCMakeLists.txt 没有 install 指令添加 install(DIRECTORY …)
No transform from xxx缺少 joint_state_publisher添加 joint_state_publisher_gui
Gazebo 黑屏/崩溃joint_state_publisher_gui 冲突仿真时注释掉它
轮子穿过地面缺少 collision添加 collision 标签
机器人飘走缺少 inertial添加惯性参数
机器人原地打滑动力轮摩擦系数太低设置 mu1, mu2 > 1.0

恭喜!

你已完成一个能用键盘控制的两轮机器人。掌握的知识:

  • ROS 2 工程标准结构
  • Xacro 机器人建模
  • Gazebo 物理仿真
  • ROS 2 与 Gazebo 桥接通信

12. 原理和总结

当你运行 teleop_twist_keyboard 并按下按键时,机器人之所以能动,是因为在 ROS 2 的世界里发生了一场精准的**“接力赛”**。

我们可以把这个过程拆解为三个核心环节:消息协议(Twist)发布订阅机制(Topic)物理执行器(Gazebo Plugin)


1)消息类型:geometry_msgs/msg/Twist

ROS 2 中,几乎所有的移动机器人都使用同一种指令格式——Twist

当你按下 i(前进)时,teleop_twist_keyboard 会生成一个结构如下的数据包:

  • Linear (线速度): x = 0.5 , y = 0.0 , z = 0.0 x=0.5, y=0.0, z=0.0 x=0.5,y=0.0,z=0.0 (代表向前冲)
  • Angular (角速度): z = 0.0 z=0.0 z=0.0 (代表不转弯)

这个数据包不关心你的机器人是长方形还是圆形,它只表达一个抽象的运动意图
输入ros2 interface show geometry_msgs/msg/Twist可以查看Twist数据类型的结构

在这里插入图片描述

2)传递的信道:/cmd_vel 话题

teleop_twist_keyboard 就像一个广播电台,它持续向一个名为 /cmd_vel (Command Velocity) 的频道发送上述的 Twist 消息。

  • 它是标准约定:在 ROS 社区中,默认所有移动机器人都应该监听 /cmd_vel 来获取运动指令。这就像所有收音机都要调到同一个频率才能听到广播一样。

在仿真运行期间输入ros2 topic list 可以查看所有话题

在这里插入图片描述


输入ros2 topic info /cmd_vel查看 /cmd_vel 的详细信息(包含类型)

在这里插入图片描述

3)关键的接棒者:Gazebo 差速驱动插件

这是最关键的一步。由于你的机器人是在仿真环境里的,它没有真实的电机。你在 Xacro 中加入的 DiffDrive 插件 充当了“翻译官”和“虚拟电机”的角色:

  1. 监听:插件在后台一直盯着 /cmd_vel 话题。
  2. 计算 (逆运动学):当它收到“线速度 0.5m/s”的指令时,它会根据你在 Xacro 里设置的参数(wheel_separation 轮距 和 wheel_radius 轮径)进行计算。
    • 公式示例:为了达到 0.5m/s,左右两个轮子分别需要转多快?
  3. 施力:计算出转速后,插件直接向 Gazebo 物理引擎中的 left_wheel_jointright_wheel_joint 施加扭矩(Torque)

4)为什么需要那个 ros_gz_bridge

你在操作时运行了一个特殊的命令:ros_gz_bridge。这是因为:

  • Teleop 节点 跑在 ROS 2 协议上。
  • Gazebo 仿真器 跑在 Gazebo Transport 协议上。

这个“桥梁”就像一个同声传译,它把 ROS 2 频道里的 /cmd_vel 抓过来,实时翻译成 Gazebo 插件能听懂的格式并扔进仿真世界里。


5)核心特性:异步与多对多

这是 ROS 2 话题机制最强大的地方:

异步性:
发布者发完就走,不需要等订阅者回话。这保证了机器人控制的实时性。

多对多:

一个话题可以有多个发布者:比如你可以同时用键盘和自动导航算法给小车发指令(虽然这会导致小车打架)。

一个话题可以有多个订阅者:比如 /cmd_vel 话题,除了 Gazebo 插件在听,你还可以开一个日志节点记录小车的运动轨迹,它们互不干扰。


5)总结:小车运动的全路径

  1. 你按下 i -> teleop_twist_keyboard 发出 Twist 消息
  2. Twist 消息 进入 /cmd_vel 话题
  3. Bridge 桥梁 将消息从 ROS 传给 Gazebo
  4. DiffDrive 插件 收到消息,计算轮速,给轮子加力
  5. Gazebo 物理引擎 计算摩擦力,机器人向前滑动

接下来,我将为小车增加一个激光雷达,用于感知周围环境。下一节的链接:【从零开始创建一个ROS2机器人工程(2)】为小车增加激光雷达感知

Read more

open-webui 高速下载&Docker本地部署集成远程Ollama

open-webui 高速下载&Docker本地部署集成远程Ollama

open-webui 镜像快速高速下载 docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/open-webui/open-webui:v0.6.9 https://docker.aityp.com/r/ghcr.io/open-webui/open-webuihttps://docker.aityp.com/r/ghcr.io/open-webui/open-webui 部署教程官网即可 https://docs.openwebui.com/https://docs.openwebui.com/ 启动Ollama在另一台机器上,默认启动,对外开放端口11434 打开ip访问限制,以便于其他机器访问 在open-webui的机器上面测试一下链接 curl http:

唤醒80年代记忆:基于百度地图的一次老式天气预报的WebGIS构建之旅

唤醒80年代记忆:基于百度地图的一次老式天气预报的WebGIS构建之旅

目录 一、省会城市信息构建 1、省会城市空间查询 2、Java后台查询 二、Java省会城市天气查询 1、与百度开放平台集成天气 2、响应对象属性介绍 3、省会天气实况展示 三、WebGIS应用构建 1、背景音乐集成 2、城市标记及天气展示 3、城市轮播 4、成果展示 四、总结 前言         在数字技术飞速发展的今天,我们常常沉浸于各种高科技带来的便捷与震撼之中,却容易忽视那些曾经陪伴我们成长、承载着时代记忆的旧事物。80年代的天气预报,便是这样一份珍贵的文化遗产。它以简洁而质朴的方式,传递着天气信息,也传递着那个时代的气息。那种对自然的敬畏、对信息的渴望,以及一家人共同分享的温馨氛围,都深深烙印在我们的记忆中。然而,随着时间的推移,天气预报的形式已经发生了翻天覆地的变化。高清的画面、精准的数据、个性化的推送……这些现代技术带来的便利固然令人欣喜,但也在一定程度上让我们失去了那份对天气预报本身的纯粹情感。于是,

【年终总结】从非科班无实习到准字节前端:我始终相信,开发之外的事,才是破局关键

【年终总结】从非科班无实习到准字节前端:我始终相信,开发之外的事,才是破局关键

目录 【年终总结】从非科班无实习到准字节前端:我始终相信,开发之外的事,才是破局关键 一、求其外,善其内 1、坚持出发点正确的博文写作 2、博文更新对我心态的淬炼 3、社区交流对我视野的启发 4、向外拓展,反哺内修 二、陷入前端则前端死,跳出前端则前端活 1、从不务正业到泛前端 2、从泛前端到大前端,从有形到无形 三、秋招多少事 四、结语         作者:watermelo37         ZEEKLOG优质创作者、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、火山KOL、支付宝合作作者,全平台博客昵称watermelo37。         一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。 --------------------------------------------------------------------- 温柔地对待温柔的人,包容的三观就是最大的温柔。