add:在基础地图上实现了实时避障以及实现了比较灵活的后撤,从而实现掉头

This commit is contained in:
陌欢醉
2026-06-04 22:42:10 +08:00
parent 5c746829db
commit f95b087143
3 changed files with 112 additions and 84 deletions

View File

@@ -0,0 +1,36 @@
<!--
阿克曼底盘自定义行为树:移除了 Spin原地旋转
恢复行为改为:清除代价地图 → 后退 → 等待
-->
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<RateController hz="1.0">
<RecoveryNode number_of_retries="1" name="ComputePathToPose">
<ComputePathToPose goal="{goal}" path="{path}" planner_id="GridBased"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</RecoveryNode>
</RateController>
<RecoveryNode number_of_retries="1" name="FollowPath">
<FollowPath path="{path}" controller_id="FollowPath"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</RecoveryNode>
</PipelineSequence>
<ReactiveFallback name="RecoveryFallback">
<GoalUpdated/>
<RoundRobin name="RecoveryActions">
<!-- 1. 清除代价地图,去掉短暂障碍 -->
<Sequence name="ClearingActions">
<ClearEntireCostmap name="ClearLocalCostmap-Subtree" service_name="local_costmap/clear_entirely_local_costmap"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Subtree" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
<!-- 2. 后退 0.5m,给重新规划留空间(阿克曼无法原地旋转) -->
<BackUp backup_dist="0.50" backup_speed="0.05"/>
<!-- 3. 等待 1s让动态障碍物离开 -->
<Wait wait_duration="1"/>
</RoundRobin>
</ReactiveFallback>
</RecoveryNode>
</BehaviorTree>
</root>

View File

@@ -1,33 +1,25 @@
# ============================================================================ # ============================================================================
# gc_nav2_with_slam_online.launch.py # gc_nav2_with_slam_online.launch.py
# 功能:基于 slam_toolbox 在线异步建图 + 定位的导航启动文件 # 功能:已知全局地图 + SLAM 在线更新 + 导航
#
# 核心能力:
# 1. 启动时加载已有地图my_map.posegraph无需从零建图
# 2. 运行中持续更新地图(新障碍物自动写入 /map
# 3. 激光扫描匹配定位(替代 AMCL自动定位
# 4. 双层避障obstacle_layer/scan 瞬时避障)+ static_layer/map 持久化障碍)
# #
# 架构: # 架构:
# Gazebo 仿真环境 # ┌─ /map全量静态地图199×266
# ├── robot_state_publisher — URDF → TF 树base_link→laser_link 等) # map_server ──────────┤
# ├── slam_toolbox — async_slam_toolbox_node # └─ 来自 my_map.yaml + my_map.pgm
# │ ├── 加载 my_map.posegraph + my_map.data
# │ ├── mode: mapping定位 + 实时建图)
# │ ├── 发布 /map全量 + 实时更新)
# │ └── 发布 map→odom TF定位结果
# └── navigation_launch — Nav2 导航栈
# ├── global_costmap ← /map静态层
# ├── local_costmap ← /scan障碍层
# ├── planner_server — SmacHybrid 路径规划
# ├── controller_server — MPPI Ackermann 控制
# ├── behavior_server — spin/backup/wait 恢复
# └── bt_navigator — 行为树编排
# #
# 与其他方案对比: # ┌─ map→odom TF激光扫描匹配定位
# gc_nav2_with_amcl.launch.py: AMCL 粒子滤波,地图固定,需手动设初始位姿 # slam_toolbox ────────┤
# gc_nav2_with_slam.launch.py: SLAM 定位模式,地图只读,需 map_server # (async mapping) └─ /slam_map实时建图结果重映射避免冲突
# gc_nav2_with_slam_online.launch.py: SLAM 在线模式,加载已有地图 + 实时更新(本文件) #
# navigation ┌─ global_costmap/static_layer ← /map全量地图
# ├─ local_costmap/obstacle_layer ← /scan实时避障
# └─ planner + controller + behavior
#
# 关键设计:
# 1. map_server 提供全量静态底图 → Foxglove 中看到完整房间布局
# 2. slam_toolbox 定位 + 实时建图,/slam_map 可单独查看更新效果
# 3. 实时避障通过 costmap 的 obstacle_layer 实现(不依赖地图更新)
# 4. 定期调用 /slam_toolbox/serialize_map 保存更新后的地图
# ============================================================================ # ============================================================================
import os import os
@@ -43,62 +35,66 @@ from launch.actions import ExecuteProcess
def generate_launch_description(): def generate_launch_description():
"""启动 Gazebo + SLAM 在线建图定位 + Nav2 导航""" """启动 Gazebo + 已知全局地图 + SLAM 在线定位建图 + Nav2 导航"""
ld = LaunchDescription() ld = LaunchDescription()
# =============================1.定位到包的地址============================================================= # =============================1.包路径================================================================
pkg_dir = get_package_share_directory('gc_navigation2_slamtoolbox') pkg_dir = get_package_share_directory('gc_navigation2_slamtoolbox')
nav2_bringup_dir = get_package_share_directory('nav2_bringup') nav2_bringup_dir = get_package_share_directory('nav2_bringup')
origincar_urdf_dir = get_package_share_directory('origincar_description') origincar_urdf_dir = get_package_share_directory('origincar_description')
# =============================2.声明路径参数=============================================================== # =============================2.参数==================================================================
# use_sim_time: 必须为 trueGazebo 通过 /clock 话题提供仿真时间
use_sim_time = LaunchConfiguration('use_sim_time', default='true') use_sim_time = LaunchConfiguration('use_sim_time', default='true')
map_yaml_path = os.path.join(pkg_dir, 'maps', 'my_map.yaml')
# nav2_param_path: Nav2 导航栈参数planner/controller/costmap/behavior
nav2_param_path = LaunchConfiguration('params_file', default=os.path.join( nav2_param_path = LaunchConfiguration('params_file', default=os.path.join(
pkg_dir, 'params', 'gc_navigation_slam.yaml')) pkg_dir, 'params', 'gc_navigation_slam.yaml'))
slam_params_file = os.path.join(pkg_dir, 'config', 'slam_toolbox_async.yaml')
# slam 基础配置文件(异步建图 + 实时更新)
slam_params_file = os.path.join(
pkg_dir, 'config', 'slam_toolbox_async.yaml')
# 已有序列化地图slam_toolbox 格式:.posegraph + .data
posegraph_path = os.path.join(pkg_dir, 'maps', 'my_map.posegraph') posegraph_path = os.path.join(pkg_dir, 'maps', 'my_map.posegraph')
# =============================3.slam_toolbox在线异步建图 + 定位============================================ # =============================3.map_server全量静态地图Foxglove 看到的全局地图)========================
# 选用 online_async_slam_toolbox_node而非 localization_slam_toolbox_node的原因 map_server_node = Node(
# - 加载已有 .posegraph 地图后,仍可继续写入新扫描数据 package='nav2_map_server',
# - 异步处理:激光数据在独立线程中建图,不阻塞里程计 executable='map_server',
# - 适合导航场景:启动时还原已知区域,运行中发现新障碍物自动更新 /map name='map_server',
# output='screen',
# 参数说明(以下参数已在 mapper_params_online_async.yaml 中配置,此处覆写确保生效): parameters=[{'yaml_filename': map_yaml_path, 'use_sim_time': True}],
# mode: mapping — 定位 + 建图(覆盖 yaml )
# map_file_name — 已在 yaml 中设为源码路径,不在此覆写 map_server_lifecycle = Node(
package='nav2_lifecycle_manager',
executable='lifecycle_manager',
name='lifecycle_manager_map',
output='screen',
parameters=[{'use_sim_time': True, 'autostart': True,
'node_names': ['map_server']}],
)
# =============================4.slam_toolbox定位 + 实时建图=============================================
# 已有序列化地图slam_toolbox 格式:.posegraph + .data # ⚠️ /map 重映射到 /slam_map避免覆盖 map_server 的全量地图
# 注意:此路径仅供文档参考,实际加载路径在 yaml 配置文件中
posegraph_path = os.path.join(pkg_dir, 'maps', 'my_map.posegraph')
slam_toolbox_node = Node( slam_toolbox_node = Node(
package='slam_toolbox', package='slam_toolbox',
executable='async_slam_toolbox_node', executable='async_slam_toolbox_node',
name='slam_toolbox', name='slam_toolbox',
output='screen', output='screen',
parameters=[ parameters=[slam_params_file, {'use_sim_time': True}],
slam_params_file, # 基础参数solver、TF、分辨率、map_file_name 等) remappings=[('/map', '/slam_map')],
{
'use_sim_time': True,
'mode': 'mapping', # 覆盖 yaml定位 + 建图
'map_update_interval': 3.0, # 每 3 秒更新一次 /map
},
],
# 无需 remap /maponline_async 发布的就是权威 /map
) )
# Read: Failed to open requested file: / home/guoch/test_ws/src/gc_navigation2_slamtoolbox/maps/my_map.posegraph.
# Failed to read file: / home/guoch/test_ws/src/gc_navigation2_slamtoolbox/maps/my_map.posegraph. # =============================5.加载已有序列化地图=======================================================
# =============================4.启动 Nav2 导航栈========================================================== # 通过 ros2 service call 在 slam_toolbox 启动后加载
# 只用 navigation_launch不含 localization因为 slam_toolbox 已承担定位 import launch
load_map_cmd = ExecuteProcess(
cmd=[
'bash', '-c',
# 等待 slam_toolbox 服务就绪后加载地图
'sleep 8 && '
'ros2 service call /slam_toolbox/deserialize_map '
'slam_toolbox/srv/DeserializePoseGraph '
"'{filename: \"%s\", match_type: 1}'" % posegraph_path,
],
output='screen',
)
# =============================6.Nav2 导航栈============================================================
navigation_launch = IncludeLaunchDescription( navigation_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource( PythonLaunchDescriptionSource(
[nav2_bringup_dir, '/launch', '/navigation_launch.py']), [nav2_bringup_dir, '/launch', '/navigation_launch.py']),
@@ -108,51 +104,43 @@ def generate_launch_description():
}.items(), }.items(),
) )
# =============================5.Gazebo 仿真环境========================================================== # =============================7.Gazebo 仿真============================================================
robot_name_in_model = 'mycar' robot_name_in_model = 'mycar'
default_model_path = os.path.join(origincar_urdf_dir, 'urdf', 'origincar.urdf') default_model_path = os.path.join(origincar_urdf_dir, 'urdf', 'origincar.urdf')
model = DeclareLaunchArgument(name='model', default_value=default_model_path) model = DeclareLaunchArgument(name='model', default_value=default_model_path)
# 启动 Gazebo 仿真器(加载 world 文件和 ROS 插件)
gazebo_world_path = os.path.join(origincar_urdf_dir, 'world', 'test.world') gazebo_world_path = os.path.join(origincar_urdf_dir, 'world', 'test.world')
start_gazebo_cmd = ExecuteProcess( start_gazebo_cmd = ExecuteProcess(
cmd=[ cmd=['gazebo', '--verbose',
'gazebo', '--verbose', '-s', 'libgazebo_ros_init.so',
'-s', 'libgazebo_ros_init.so', # ROS ↔ Gazebo 通信初始化 '-s', 'libgazebo_ros_factory.so',
'-s', 'libgazebo_ros_factory.so', # 模型生成工厂 gazebo_world_path],
gazebo_world_path,
],
output='screen', output='screen',
) )
# 在 Gazebo 中生成机器人
spawn_entity_cmd = Node( spawn_entity_cmd = Node(
package='gazebo_ros', package='gazebo_ros',
executable='spawn_entity.py', executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', default_model_path], arguments=['-entity', robot_name_in_model, '-file', default_model_path],
output='screen', output='screen',
) )
# 发布机器人 TF 树
robot_description = ParameterValue( robot_description = ParameterValue(
Command(['xacro ', LaunchConfiguration('model')]), value_type=str, Command(['xacro ', LaunchConfiguration('model')]), value_type=str,
) )
robot_state_publisher = Node( robot_state_publisher = Node(
package='robot_state_publisher', package='robot_state_publisher',
executable='robot_state_publisher', executable='robot_state_publisher',
parameters=[{ parameters=[{'robot_description': robot_description,
'robot_description': robot_description, 'use_sim_time': True, 'publish_frequency': 30.0}],
'use_sim_time': True,
'publish_frequency': 30.0,
}],
) )
# =============================6.组装 LaunchDescription=================================================== # =============================8.组装==================================================================
ld.add_action(model) ld.add_action(model)
ld.add_action(start_gazebo_cmd) ld.add_action(start_gazebo_cmd)
ld.add_action(spawn_entity_cmd) ld.add_action(spawn_entity_cmd)
ld.add_action(robot_state_publisher) ld.add_action(robot_state_publisher)
ld.add_action(map_server_node)
ld.add_action(map_server_lifecycle)
ld.add_action(slam_toolbox_node) ld.add_action(slam_toolbox_node)
ld.add_action(load_map_cmd)
ld.add_action(navigation_launch) ld.add_action(navigation_launch)
return ld return ld

View File

@@ -10,6 +10,8 @@ bt_navigator:
odom_topic: /odom odom_topic: /odom
bt_loop_duration: 20 bt_loop_duration: 20
default_server_timeout: 20 default_server_timeout: 20
# 阿克曼底盘专用 BT移除 Spin恢复行为 = 清代价地图 → 后退 → 等待
default_nav_to_pose_bt_xml: /home/guoch/test_ws/install/gc_navigation2_slamtoolbox/share/gc_navigation2_slamtoolbox/config/nav_to_pose_ackermann.xml
plugin_lib_names: plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node - nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node - nav2_compute_path_through_poses_action_bt_node
@@ -269,7 +271,7 @@ planner_server:
analytic_expansion_ratio: 3.5 analytic_expansion_ratio: 3.5
analytic_expansion_max_length: 3.0 analytic_expansion_max_length: 3.0
minimum_turning_radius: 0.40 minimum_turning_radius: 0.40
reverse_penalty: 2.0 reverse_penalty: 1.3 # 后退惩罚,越小越愿意后退(默认 2.0,设为 1.3 允许灵活倒车)
change_penalty: 0.0 change_penalty: 0.0
non_straight_penalty: 1.2 non_straight_penalty: 1.2
cost_penalty: 2.0 cost_penalty: 2.0
@@ -308,11 +310,13 @@ behavior_server:
cycle_frequency: 10.0 cycle_frequency: 10.0
behavior_plugins: ["spin", "backup", "wait"] behavior_plugins: ["spin", "backup", "wait"]
spin: spin:
plugin: "nav2_behaviors/Spin" plugin: "nav2_behaviors/Spin" # 需保留以匹配默认 BT XML
backup: backup:
plugin: "nav2_behaviors/BackUp" plugin: "nav2_behaviors/BackUp"
backup_dist: 0.5
wait: wait:
plugin: "nav2_behaviors/Wait" plugin: "nav2_behaviors/Wait"
wait_duration: 0.5
global_frame: odom global_frame: odom
robot_base_frame: base_link robot_base_frame: base_link
transform_tolerance: 0.5 transform_tolerance: 0.5