将 MoveIt 2move_group规划的轨迹通过moveit_servo发送给机械臂,核心思路是将move_group生成的完整轨迹,按时间步切片后,逐个通过moveit_servo的实时控制接口发送出去。
move_group擅长全局路径规划,而moveit_servo擅长实时轨迹跟踪。两者可以形成“规划-跟踪”的闭环,实现更流畅和可控的运动。
核心工作流程
整个流程可以分为四个主要步骤:
获取轨迹:使用
move_group规划出一条完整的运动轨迹(RobotTrajectory)。配置 Servo:配置
moveit_servo节点,使其能接收外部指令。解析与发送:解析轨迹中的每一个点,并将其转换为
moveit_servo能理解的指令格式,按预定时间间隔逐个发布。底层执行:
moveit_servo接收到指令后,通过其内部的控制律(如导纳控制)生成平滑的关节扭矩或速度指令,最终发送给真实的机器人硬件控制器。
下面,我们将逐步拆解这个流程。
步骤一:使用move_group规划轨迹
这是流程的起点。你需要通过MoveGroupInterface规划出一条从起点到目标点的无碰撞轨迹。
cpp
// 1. 初始化 MoveGroupInterface auto move_group_node = rclcpp::Node::make_shared("move_group_interface_demo"); moveit::planning_interface::MoveGroupInterface move_group(move_group_node, "your_arm_group"); // 2. 设置目标位姿 move_group.setPoseTarget(target_pose); // 3. 规划运动(得到一个完整的轨迹) moveit::planning_interface::MoveGroupInterface::Plan my_plan; bool success = (move_group.plan(my_plan) == moveit::core::MoveItErrorCode::SUCCESS); if (success) { // 规划成功,轨迹数据存储在 my_plan.trajectory_ 中 RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "规划成功!准备通过 Servo 发送..."); // 保存轨迹以备后续发送 robot_trajectory_ptr = std::make_shared<robot_trajectory::RobotTrajectory>(my_plan.trajectory_); } else { RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "规划失败!"); }关键点:
你需要根据机械臂的配置正确初始化
MoveGroupInterface,包括规划组名称、基座坐标系等。规划成功后,
my_plan.trajectory_中包含了完整的关节空间轨迹点(位置、速度、加速度和时间戳)。
步骤二:配置moveit_servo接收轨迹指令
moveit_servo默认是为实时交互设计的(如遥操作)。为了让其能执行预规划的轨迹,需要正确配置其输入接口。
在moveit_servo的配置 YAML 文件(如moveit_servo.yaml)中,你需要关注以下参数:
yaml
# moveit_servo.yaml 中与接收轨迹相关的关键配置 # 1. 指令发布周期(秒),决定了轨迹跟踪的平滑度 publish_period: 0.01 # 100Hz,常见值,可根据机器人性能调整 # 2. 指令输出类型和话题 command_out_type: trajectory_msgs/JointTrajectory # 输出完整的轨迹点 command_out_topic: /your_robot_controller/joint_trajectory # 发送给底层控制器的话题 # 3. MoveIt 相关设置,必须与你的机器人匹配 move_group_name: your_arm_group # 规划组名称,需与步骤一一致 planning_frame: base_link # 规划参考坐标系 ee_frame_name: your_ee_link # 末端执行器连杆名称 robot_link_command_frame: base_link # 参考坐标系 # 4. 发布关节位置和速度(让控制器获得完整信息) publish_joint_positions: true publish_joint_velocities: true
关键点:
publish_period应与轨迹点的时间间隔相匹配,或者更高,以确保指令的连续性。command_out_topic必须指向你的机械臂控制器的正确话题名。
步骤三:解析轨迹并发送给 Servo
这是将两者连接起来的核心逻辑。你需要写一个节点,接收move_group的规划结果,将其转换为moveit_servo能理解的JointTrajectory消息,并按时序发布出去。
python
import rclpy from rclpy.node import Node from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint class TrajectoryExecutor(Node): def __init__(self): super().__init__('trajectory_executor') # 创建一个发布者,向 moveit_servo 发送指令 self.servo_pub = self.create_publisher(JointTrajectory, '/servo_node/delta_joint_cmds', 10) self.timer = None self.trajectory_points = [] # 存储所有轨迹点 self.current_point_idx = 0 def execute_planned_trajectory(self, planned_trajectory): """ 将 move_group 规划的轨迹转换为 serv 指令并发送 参数: planned_trajectory: 来自 MoveGroupInterface::Plan 的轨迹数据 """ # 1. 提取所有轨迹点 self.trajectory_points = planned_trajectory.joint_trajectory.points if not self.trajectory_points: self.get_logger().error("接收到的轨迹为空!") return # 2. 获取第一个点的时间作为基准 start_time = self.get_clock().now() # 3. 启动定时器,按时间点发送指令 self.timer = self.create_timer(0.01, self.send_next_point) # 10ms 定时器 def send_next_point(self): if self.current_point_idx >= len(self.trajectory_points): # 轨迹发送完毕,停止定时器 self.timer.cancel() self.get_logger().info("轨迹执行完毕!") return # 获取当前需要发送的轨迹点 point = self.trajectory_points[self.current_point_idx] # 构造 JointTrajectory 消息 msg = JointTrajectory() msg.header.stamp = self.get_clock().now().to_msg() # 重要:关节名称必须与 move_group 和伺服驱动器的配置一致 msg.joint_names = ["joint1", "joint2", "joint3", "joint4", "joint5", "joint6"] msg.points.append(point) # 发布给 moveit_servo self.servo_pub.publish(msg) # 计算下一个点应该发送的时间(根据轨迹点自带的时间戳) # 简化版本:每 10ms 发送下一个点 # 精确版本:应根据点的 time_from_start 来等待 self.current_point_idx += 1关键点:
坐标系一致性:
msg.joint_names必须与move_group规划轨迹时使用的关节名称完全一致。时间控制:发送指令的节奏应与轨迹点的时间戳(
time_from_start)匹配。上例为了简洁使用了固定周期,生产环境应使用更精确的定时机制。消息类型:
moveit_servo通常订阅/servo_node/delta_joint_cmds或/servo_node/delta_twist_cmds话题。你需要查阅你的 Servo 配置确定具体话题名。
步骤四:moveit_servo接管并发送给硬件
moveit_servo节点持续收到你发布的JointTrajectory点后,它的内部机制(如导纳控制器)会将这些离散的目标点转换为平滑的、实时的底层指令(关节扭矩或速度),并最终发布到机器人的硬件接口上(command_out_topic)。这个步骤由moveit_servo自动完成,你无需干预,但需要确保:
moveit_servo节点已正确启动并运行。/your_robot_controller/joint_trajectory话题被正确的机器人控制器订阅。机器人的硬件驱动器处于使能状态。
流程图
整个过程的数据流和模块交互可以总结如下:
总结
将move_group规划的轨迹传递给moveit_servo执行,是一种将“规划”与“跟踪”解耦的架构模式。实现这一模式的关键在于:
确保接口匹配:即关节名称、消息类型(
JointTrajectory)、通信话题在move_group、你的桥接节点和moveit_servo三者之间保持完全一致。精确的时间控制:发送轨迹点的节奏需要与轨迹自带的时间戳对齐,否则会导致运动速度偏离预期。
深入理解流程:不仅要会写代码,更要理解
move_group的规划输出格式和moveit_servo的指令输入格式。
这是一个进阶用法。你目前的moveit_servo配置文件中command_out_type设置的是什么?如果你能提供当前的配置,我可以帮你进一步校准执行器的参数。