#!/usr/bin/env python3 """P5 精准定位:激光 + rf2o 激光里程计 + 已知地图 + AMCL。 完整 TF 树:map ─(amcl)→ odom ─(rf2o)→ base_footprint ─(静态)→ laser_frame odom→base_footprint 由 rf2o 从 /scan 连续提供(不依赖底盘里程计板),AMCL 用它做运动模型、 激光匹配已知地图做校正,输出绝对、无漂移位姿。需要 P4 建好并存盘的地图。 说明:底盘轮式里程计板上电后仅短时上报,暂不参与 odom→base;待其连续上报问题解决后, 可用 robot_localization EKF 融合轮速 + rf2o 再喂 AMCL 提升鲁棒性。 用法: ros2 launch craic_localization localization.launch.py map:=/abs/path/maps/craic.yaml # 起点不在地图原点时,rviz 用 "2D Pose Estimate" 给初值,或: ros2 launch craic_localization localization.launch.py map:=... init_x:=0.3 init_y:=0.2 init_yaw:=1.57 """ import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.conditions import IfCondition from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node from launch_ros.descriptions import ParameterValue def generate_launch_description(): pkg_share = get_package_share_directory('craic_localization') lidar_launch = os.path.join(pkg_share, 'launch', 'lidar.launch.py') amcl_params = os.path.join(pkg_share, 'config', 'amcl.yaml') default_rviz = os.path.join(pkg_share, 'rviz', 'localization.rviz') default_map = os.path.join(pkg_share, 'maps', 'craic.yaml') use_rviz = LaunchConfiguration('use_rviz') map_yaml = LaunchConfiguration('map') init_x = LaunchConfiguration('init_x') init_y = LaunchConfiguration('init_y') init_yaw = LaunchConfiguration('init_yaw') show_points = LaunchConfiguration('show_points') points_file = LaunchConfiguration('points_file') return LaunchDescription([ DeclareLaunchArgument('use_rviz', default_value='true'), DeclareLaunchArgument('map', default_value=default_map, description='地图 yaml 路径(P4 由 map_saver 存盘得到)'), DeclareLaunchArgument('init_x', default_value='0.0'), DeclareLaunchArgument('init_y', default_value='0.0'), DeclareLaunchArgument('init_yaw', default_value='0.0'), DeclareLaunchArgument('show_points', default_value='true'), DeclareLaunchArgument('points_file', default_value=os.path.join(pkg_share, 'config', 'taught_points.yaml'), description='示教点位文件(rviz 显示);想实时看示教就指向你的 taught_points.yaml'), # ===== 传感器 + 激光里程计(odom -> base_footprint) ===== IncludeLaunchDescription(PythonLaunchDescriptionSource(lidar_launch)), Node( package='rf2o_laser_odometry', executable='rf2o_laser_odometry_node', name='rf2o_laser_odometry', output='screen', parameters=[{ 'laser_scan_topic': '/scan', 'odom_topic': '/odom', 'publish_tf': True, 'base_frame_id': 'base_footprint', 'odom_frame_id': 'odom', 'init_pose_from_topic': '', 'freq': 10.0, }], ), # ===== 地图服务 ===== Node( package='nav2_map_server', executable='map_server', name='map_server', output='screen', parameters=[amcl_params, {'yaml_filename': map_yaml}], ), # ===== AMCL 定位 ===== Node( package='nav2_amcl', executable='amcl', name='amcl', output='screen', parameters=[amcl_params, { 'set_initial_pose': True, 'initial_pose.x': ParameterValue(init_x, value_type=float), 'initial_pose.y': ParameterValue(init_y, value_type=float), 'initial_pose.yaw': ParameterValue(init_yaw, value_type=float), }], ), # ===== 生命周期管理:自动 configure+activate map_server 与 amcl ===== Node( package='nav2_lifecycle_manager', executable='lifecycle_manager', name='lifecycle_manager_localization', output='screen', parameters=[{ 'use_sim_time': False, 'autostart': True, 'node_names': ['map_server', 'amcl'], }], ), # ===== 示教点位可视化 ===== Node( package='craic_localization', executable='show_points', name='show_points', output='screen', parameters=[{'points_file': points_file}], condition=IfCondition(show_points), ), Node( package='rviz2', executable='rviz2', name='rviz2', arguments=['-d', default_rviz], condition=IfCondition(use_rviz), output='screen', ), ])