点击查看代码
import launch
import launch_ros
from ament_index_python.packages import get_package_share_directory
from launch_ros import parameter_descriptions
import os
def generate_launch_description():
"""
ROS2 launch启动文件主函数
功能:加载fishbot机器人XACRO模型+Gazebo仿真环境
"""
# 获取功能包的share路径
# get_package_share_directory:获取功能包安装后的share目录路径(install/xxx/share/fishbot_description)
urdf_package_path=get_package_share_directory('fishbot_description')
# 拼接机器人xacro模型完整路径 urdf/fishbot/fishbot.urdf.xacro
default_xacro_path=os.path.join(urdf_package_path,'urdf','fishbot/fishbot.urdf.xacro')
# 拼接Gazebo仿真世界文件路径 world/custom_room.world
default_gazebo_world_config_path=os.path.join(urdf_package_path,'world','custom_room.world')
# ===============2.声明启动参数 LaunchArgument =================
# DeclareLaunchArgument:对外暴漏启动参数:命令行启动时可以覆盖默认模型路径
# 使用示例: ros2 launch fishbot_description gazebo.launch.py model:=:xxx/xxx.urdf.xacro
action_declare_arg_mode_path=launch.actions.DeclareLaunchArgument(
name='model',default_value=str(default_xacro_path),description='机器人XACRO/URDF模型文件路径'
)
# ===============3.执行xacro命令,解析模型文件===============================
# LaunchConfiguration('model'):获取上面声明的model参数的值
# Command():等价与终端执行 'xacro模型文件',将xacro编译为标准urdf文本
substitutions_command_result=launch.substitutions.Command(['xacro ',launch.substitutions.LaunchConfiguration
('model')])
# ParameterValue:把xacro输出的URDF字符串封装成ROS参数,提供给robot_state_publisher
robot_description_value=launch_ros.parameter_descriptions.ParameterValue(substitutions_command_result,
value_type=str)
#================4.启动robot_state_publisher节点============================
# 作用:
# 1.读取robot_description参数(URDF模型)
# 2.根据关节状态发布TF坐标变换
action_robot_state_publisher=launch_ros.actions.Node(
package='robot_state_publisher', #包名
executable='robot_state_publisher',# 可行程序名
parameters=[{'robot_description': robot_description_value}], #传入URDF参数
)
# =============5.引入gazebo官方启动文件,启动仿真==========================
# IncludeLaunchDescription:在当前launch里嵌套另一个launch.py(类似C语言#include)
# IncludeLaunchDescription 动作-----要嵌入另一个launch文件
action_launch_gazebo=launch.actions.IncludeLaunchDescription(
# PythonLaunchDescriptionSource(路径):告诉launch系统,这个路径对应的文件是.launch.py 按python方式解析执行
launch.launch_description_sources.PythonLaunchDescriptionSource(
# 拼接 gazebo_ros包下 gazebo.launch.py的完整路径
[get_package_share_directory('gazebo_ros'),'/launch','/gazebo.launch.py']
),
# 向gazebo.launch.py传递启动参数
# world:指定要加载的仿真场景world文件
# verbose='true': 开启gazebo详细日志输出,方便排错
launch_arguments=[('world',default_gazebo_world_config_path),('verbose','true')]
)
# 定义动作:在Gazebo仿真环境中生成机器人实体
action_spawn_entity=launch_ros.actions.Node(
# 指定功能包名称:gazebo_ros,Gazebo与ROS2通信的官方功能包
package='gazebo_ros',
#要运行的可执行文件:spawn_entity.py
# 作用:读取机器人模型描述,将机器人模型加载进Gazebo世界
executable='spawn_entity.py',
# 传入命令行参数列表,控制spawn_entity的行为
arguments=[
#-topic:指定从哪个ROS话题获取机器人模型描述URDF/XACRO字符串
# /robot_description是ROS机器人标准话题,存储机器人完整模型描述
# -entity:设置Gazebo中机器人实体名称
# Gazebo内通过该名字区分多个机器人,后续控制、获取传感器数据会用到
'-topic','/robot_description','-entity','fishbot']
)
# =================ROS2 Control 控制器加载指令======================
# ExecuteProcess:执行中断命令,等价于在终端手动执行 ros2 control load_controller
# --set-state active:加载控制器并直接激活
# 1.关节状态广播器:发布所有非驱动关节(没有电机控制、不能主动施加动力的关节)的状态(位置、速度),供RVIZ可视化
load_joint_state_controller=launch.actions.ExecuteProcess(
cmd=['ros2','control','load_controller','--set-state','active',
'fishbot_joint_state_broadcaster'], # TODO 问题 fishbot_joint_state_broadcaster 在哪用到的?
output='screen'#将命令输出打印到终端
)
# 2.力矩控制器effort_controller
# 作用:基于力矩/扭矩模式驱动关节,适合需要精确力控、柔顺控制的机械臂、伺服系统
# 本fishbot小鱼底盘为两轮差速轮式机器人,使用速度模式差速控制器即可,暂时不需要力矩控制
# 代码保留定义作为模版备用,后续如果更换力矩驱动底盘、机械臂时可以直接接入事件链使用
# ExecuteProcess:调用终端命令ros2 control load_controller,加载控制器并直接激活
load_fishbot_effort_controller=launch.actions.ExecuteProcess(
cmd=['ros2','control','load_controller','--set_state','active',
'fishbot_effort_controller'],
output='screen'
)
#3.差分驱动控制器:diff_drive_controller
# ROS2 Control官方提供的两轮两轮差速底盘专用控制器,是轮式移动机器人核心
# 订阅话题:/cmd_vel(geometry_msgs/msg/Twist),接收速度指令(线速度、角速度)
# 内部算法:将整体线速度、角速度换算成左右两轮各自目标轮速
# 输出指令给机器人左右驱动关节,实现前进、后退、原地转向、圆弧行驶
# 依赖:URDF中正确配置传动关节、ros2_control硬件接口(Position/Velocity接口)
# --set-state active 参数含义:加载控制器后立刻切换为激活状态,直接就绪接收指令
load_fishbot_diff_drive_controller=launch.actions.ExecuteProcess(
cmd=['ros2','control','load_controller','--set-state','active','fishbot_diff_drive_controller'],
output='screen'#输出日志,便于调试:查看控制器是否加载成功、有无参数报错
)
# ======核心修复:延迟3秒启动spawn,等待Gazebo加载完毕======
delayed_spawn = launch.actions.TimerAction(
period=3.0,
actions=[action_spawn_entity]
)
# ===============6.组织所有动作,返回启动描述对象=============
# LaunchDescription接收一个列表,按顺序执行里面所有动作
return launch.LaunchDescription([
action_declare_arg_mode_path, # 先声明参数
action_robot_state_publisher, # 启动机器人状态发布器
action_launch_gazebo, #启动Gazebo仿真环境
# action_spawn_entity,
delayed_spawn,
# launch.actions.RegisterEventHandler(
# event_handler=launch.event_handler.OnProcessExit(
# target_action=action_spawn_entity,
# )
# ),
])
原因:launch 文件里所有动作默认【几乎同时并发启动】,没有先后等待逻辑;Gazebo 启动、加载 ROS 桥插件是慢操作,spawn 客户端跑得太快,插件还没启动,自然找不到 /spawn_entity 服务。
解决方法:用delayed_spawn替换action_spawn_entity