功能包整合与 GitHub 管理
0. 为什么要整合
前两篇笔记完成了导航仿真的搭建,但存在一个问题:部分配置需要直接修改系统文件(/opt/ros/humble/...),这导致:
• 换一台机器就要重新手动修改系统文件 • 无法通过 git clone 直接复现环境 • 代码散落在系统各处,不便管理和分享
正确做法是将所有文件收进一个标准 ROS2 功能包,自己的 launch 文件加载自己包里的资源,完全不依赖修改系统文件。
1. 目标目录结构
整合后的功能包结构如下:
campus_nav/
├── config/
│ ├── ekf.yaml # EKF 融合参数
│ ├── navsat.yaml # GPS 坐标转换参数
│ └── nav2_params.yaml # Nav2 导航参数
├── launch/
│ ├── bringup.launch.py # 一键启动(推荐使用)
│ ├── sim.launch.py # 仅启动仿真环境
│ ├── localization.launch.py
│ └── nav2.launch.py
├── models/
│ └── turtlebot3_waffle/
│ ├── model.sdf # 加入 GPS 传感器的 SDF
│ └── model.config
├── urdf/
│ ├── turtlebot3_waffle.urdf # 加入 GPS link 的 URDF
│ └── common_properties.urdf
├── worlds/
│ └── turtlebot3_world.world # 设置天津坐标参考点
├── CMakeLists.txt
├── package.xml
├── .gitignore
└── README.mdNOTE
⚠ 关键原则:所有曾经修改过的系统文件(URDF、SDF、world)都复制到功能包里统一管理,不再依赖系统路径。
2. package.xml
package.xml 声明包的基本信息和依赖关系,是 ROS2 功能包的必要文件:
<?xml version="1.0"?>
<package format="3">
<name>campus_nav</name>
<version>0.1.0</version>
<description>Campus autonomous navigation based on RTK + Nav2</description>
<maintainer email="your@email.com">your_name</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- 运行时依赖 -->
<exec_depend>robot_localization</exec_depend>
<exec_depend>nav2_bringup</exec_depend>
<exec_depend>turtlebot3_gazebo</exec_depend>
<exec_depend>gazebo_ros</exec_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>3. CMakeLists.txt
CMakeLists.txt 告诉 colcon 编译器如何处理这个包。对于纯配置包(无 C++ 代码),核心是把所有目录安装到 share 路径下:
cmake_minimum_required(VERSION 3.8)
project(campus_nav)
find_package(ament_cmake REQUIRED)把所有资源目录安装到 share/campus_nav/
install(DIRECTORY
config
launch
urdf
worlds
models
DESTINATION share/${PROJECT_NAME}
)
ament_package()说明:install(DIRECTORY ...) 执行后,colcon build 会把这些目录复制到 install/campus_nav/share/campus_nav/,launch 文件里用 get_package_share_directory('campus_nav') 就能找到它们。
4. bringup.launch.py —— 一键启动
bringup.launch.py 是整个系统的总入口,按顺序启动所有节点。使用 TimerAction 控制启动时序,避免节点依赖问题:
import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, TimerAction, SetEnvironmentVariable
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
pkg = get_package_share_directory('campus_nav')
pkg_gz_ros = get_package_share_directory('gazebo_ros')
pkg_tb3_gz = get_package_share_directory('turtlebot3_gazebo')
pkg_nav2 = get_package_share_directory('nav2_bringup')
urdf_path = os.path.join(pkg, 'urdf', 'turtlebot3_waffle.urdf')
world_path = os.path.join(pkg, 'worlds', 'turtlebot3_world.world')
ekf_yaml = os.path.join(pkg, 'config', 'ekf.yaml')
navsat_yaml = os.path.join(pkg, 'config', 'navsat.yaml')
nav2_yaml = os.path.join(pkg, 'config', 'nav2_params.yaml')
with open(urdf_path, 'r') as f:
robot_desc = f.read()
return LaunchDescription([
# ── 1. 仿真环境(立即启动)──────────────────
SetEnvironmentVariable(
name='GAZEBO_MODEL_PATH',
value=os.path.join(pkg, 'models') + ':' +
os.path.join(pkg_tb3_gz, 'models')
),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gz_ros, 'launch', 'gzserver.launch.py')
),
launch_arguments={'world': world_path}.items()
),
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gz_ros, 'launch', 'gzclient.launch.py')
)
),
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_desc, 'use_sim_time': True}]
),
Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=[
'-entity', 'waffle',
'-file', os.path.join(pkg, 'models', 'turtlebot3_waffle', 'model.sdf'),
'-x', '-2.0', '-y', '-0.5', '-z', '0.01'
],
output='screen'
),
# ── 2. EKF 定位(延迟 3s 等 Gazebo 就绪)────
TimerAction(period=3.0, actions=[
Node(
package='robot_localization',
executable='ekf_node',
name='ekf_filter_node',
parameters=[ekf_yaml, {'use_sim_time': True}],
output='screen'
),
]),
# ── 3. navsat_transform(延迟 6s 等 EKF 就绪)
TimerAction(period=6.0, actions=[
Node(
package='robot_localization',
executable='navsat_transform_node',
name='navsat_transform',
parameters=[navsat_yaml, {'use_sim_time': True}],
output='screen'
),
]),
# ── 4. Nav2(延迟 8s)────────────────────────
TimerAction(period=8.0, actions=[
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_nav2, 'launch', 'navigation_launch.py')
),
launch_arguments={
'use_sim_time': 'true',
'params_file': nav2_yaml,
}.items()
),
]),
# ── 5. Rviz(延迟 10s)───────────────────────
TimerAction(period=10.0, actions=[
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_nav2, 'launch', 'rviz_launch.py')
),
launch_arguments={'use_sim_time': 'true'}.items()
),
]),
])说明:TimerAction 的延迟时间是经验值。Gazebo 启动最慢需要约 3 秒,EKF 需要等 Gazebo 的话题就绪,navsat_transform 需要等 EKF 输出 /odometry/filtered,Nav2 最后启动。
5. 编译与验证
cd ~/carplaning/nav_ws
colcon build --packages-select campus_nav
source install/setup.bash设置机器人型号
export TURTLEBOT3_MODEL=waffle一键启动
ros2 launch campus_nav bringup.launch.py约 10 秒后 Rviz 打开,看到激光扫描红点出现后,点击顶部工具栏 Nav2 Goal 设置目标点,小车开始自动导航。
6. Git 分支规划
本项目分仿真和实物两个阶段,用两个分支管理:
simulation ← 仿真版本(当前,长期保留)
hardware ← 实物版本(后续从 simulation 切出来修改)
选择分支而非两个仓库的原因:两套代码共享同一套导航逻辑(Nav2配置、EKF配置),只有传感器驱动层不同。用分支可以清楚追踪从仿真到实物改了哪些文件。
6.1 初始化仓库并推送
cd ~/carplaning/nav_ws/src/campus_nav
git init
git add .
git commit -m "feat: 仿真阶段完成 - Turtlebot3 + EKF + Nav2 无图导航"将当前分支命名为 simulation
git branch -M simulation关联远程仓库并推送
git remote add origin https://github.com/你的用户名/仓库名.git
git push -u origin simulation6.2 后续创建实物分支
当开始实物阶段时,从 simulation 分支切出 hardware 分支:
# 基于仿真分支创建实物分支
git checkout -b hardware
# 在 hardware 分支上修改(替换传感器驱动等)
# ...
git add .
git commit -m "feat: 实物阶段 - 接入 UM982 + Mid-360 + 真实底盘"
git push -u origin hardware6.3 日常工作流
# 切换到仿真分支
git checkout simulation
# 切换到实物分支
git checkout hardware
# 查看两个分支的差异
git diff simulation hardware
# 把仿真分支的某个提交同步到实物分支
git checkout hardware
git cherry-pick <commit-hash>7. .gitignore
避免提交编译产物和 Python 缓存:
# ROS2 编译产物(不在包目录里,但以防万一)
*.pyc
__pycache__/
*.egg-info/
# 如果整个 nav_ws 都在 git 里,忽略 build/install/log
build/
install/
log/建议:只把 src/campus_nav/ 目录初始化为 git 仓库,不要把整个 nav_ws 都放进去,build/ 和 install/ 目录很大且完全可以重新编译生成。
