|
|
#!/usr/bin/env python3
|
|
|
import rclpy
|
|
|
from rclpy.node import Node
|
|
|
from geometry_msgs.msg import Twist, PoseStamped, TransformStamped
|
|
|
from nav_msgs.msg import Path
|
|
|
from visualization_msgs.msg import Marker
|
|
|
from tf2_ros import TransformBroadcaster
|
|
|
import math
|
|
|
|
|
|
|
|
|
class TShapeController(Node):
|
|
|
"""T字形轨迹控制器 - PID角度控制 + 智能动态距离计算版本"""
|
|
|
|
|
|
def __init__(self):
|
|
|
super().__init__('t_shape_controller')
|
|
|
|
|
|
# 声明ROS参数 - 可通过Launch文件或命令行设置
|
|
|
self.declare_parameter('t_width', 3.0) # T字横杠宽度(米)
|
|
|
self.declare_parameter('t_height', 2.0) # T字竖杠高度(米)
|
|
|
self.declare_parameter('linear_speed', 0.3) # 直线速度(米/秒)
|
|
|
self.declare_parameter('angular_speed', 0.5) # 最大角速度(弧度/秒)
|
|
|
self.declare_parameter('loop_enabled', True) # 是否循环运动
|
|
|
|
|
|
# PID参数声明(角度控制)
|
|
|
self.declare_parameter('pid_kp', 1.5) # 比例系数
|
|
|
self.declare_parameter('pid_ki', 0.01) # 积分系数
|
|
|
self.declare_parameter('pid_kd', 0.15) # 微分系数
|
|
|
self.declare_parameter('angle_tolerance', 0.01) # 角度容差(弧度,约0.57度)
|
|
|
|
|
|
# 线性运动角度校正参数
|
|
|
self.declare_parameter('linear_angle_tolerance', 0.05) # 线性运动角度容差(弧度,约2.9度)
|
|
|
self.declare_parameter('angle_correction_speed', 0.3) # 角度校正速度(弧度/秒)
|
|
|
|
|
|
# 获取参数值
|
|
|
self.t_width = self.get_parameter('t_width').value
|
|
|
self.t_height = self.get_parameter('t_height').value
|
|
|
self.linear_speed = self.get_parameter('linear_speed').value
|
|
|
self.angular_speed = self.get_parameter('angular_speed').value
|
|
|
self.loop_enabled = self.get_parameter('loop_enabled').value
|
|
|
|
|
|
# 获取PID参数
|
|
|
self.pid_kp = self.get_parameter('pid_kp').value
|
|
|
self.pid_ki = self.get_parameter('pid_ki').value
|
|
|
self.pid_kd = self.get_parameter('pid_kd').value
|
|
|
self.angle_tolerance = self.get_parameter('angle_tolerance').value
|
|
|
|
|
|
# 获取线性运动角度校正参数
|
|
|
self.linear_angle_tolerance = self.get_parameter('linear_angle_tolerance').value
|
|
|
self.angle_correction_speed = self.get_parameter('angle_correction_speed').value
|
|
|
|
|
|
# 创建发布器
|
|
|
self.cmd_vel_pub = self.create_publisher(Twist, '/cmd_vel', 10) # 速度指令
|
|
|
self.path_pub = self.create_publisher(Path, '/robot_path', 10) # 运动路径
|
|
|
self.marker_pub = self.create_publisher(Marker, '/t_shape_marker', 10) # 参考轨迹
|
|
|
|
|
|
# TF广播器 - 发布odom->base_link变换
|
|
|
self.tf_broadcaster = TransformBroadcaster(self)
|
|
|
|
|
|
# 机器人状态变量
|
|
|
self.x = 0.0 # X坐标(米)
|
|
|
self.y = 0.0 # Y坐标(米)
|
|
|
self.theta = 0.0 # 航向角(弧度)
|
|
|
self.current_segment = 0 # 当前运动段(0-9,共10个阶段)
|
|
|
self.segment_start_time = None # 当前段开始时间
|
|
|
|
|
|
# 关键点坐标记录(智能动态距离计算)
|
|
|
self.t_center_x = None # T字中心点X坐标
|
|
|
self.t_center_y = None # T字中心点Y坐标
|
|
|
self.start_x = None # 起点X坐标
|
|
|
self.start_y = None # 起点Y坐标
|
|
|
self.right_end_x = None # 右端点X坐标
|
|
|
self.right_end_y = None # 右端点Y坐标
|
|
|
|
|
|
# 每个阶段起点坐标(用于距离判断)
|
|
|
self.segment_start_x = 0.0
|
|
|
self.segment_start_y = 0.0
|
|
|
|
|
|
# PID角度控制状态变量
|
|
|
self.target_angle = 0.0 # 目标角度(弧度)
|
|
|
self.error_sum = 0.0 # 误差积分累积
|
|
|
self.last_error = 0.0 # 上一次误差(用于微分)
|
|
|
self.pid_active = False # PID是否激活
|
|
|
|
|
|
# 线性运动角度校正状态
|
|
|
self.linear_target_angle = 0.0 # 线性运动目标角度
|
|
|
self.angle_correction_active = False # 角度校正是否激活
|
|
|
|
|
|
# 路径记录消息
|
|
|
self.path_msg = Path()
|
|
|
self.path_msg.header.frame_id = 'odom'
|
|
|
|
|
|
# 创建定时器:控制循环(50Hz)
|
|
|
self.control_timer = self.create_timer(0.02, self.control_loop)
|
|
|
|
|
|
# 创建定时器:发布路径和TF(10Hz)
|
|
|
self.publish_timer = self.create_timer(0.1, self.publish_status)
|
|
|
|
|
|
# 发布T字形轨迹参考标记
|
|
|
self.publish_t_shape_marker()
|
|
|
|
|
|
self.get_logger().info('T字形控制器已启动(PID角度控制 + 智能距离计算 + 线性角度校正)')
|
|
|
self.get_logger().info(f'轨迹参数: 宽度={self.t_width}m, 高度={self.t_height}m, 速度={self.linear_speed}m/s')
|
|
|
self.get_logger().info(f'PID参数: Kp={self.pid_kp}, Ki={self.pid_ki}, Kd={self.pid_kd}')
|
|
|
self.get_logger().info(f'线性角度校正: 容差={self.linear_angle_tolerance:.3f}rad, 校正速度={self.angle_correction_speed}rad/s')
|
|
|
|
|
|
def control_loop(self):
|
|
|
"""主控制循环 - 50Hz频率执行"""
|
|
|
current_time = self.get_clock().now()
|
|
|
|
|
|
# 初始化段开始时间
|
|
|
if self.segment_start_time is None:
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
twist = Twist()
|
|
|
|
|
|
# T字形运动分为10个阶段(PID角度控制 + 智能距离计算 + 线性角度校正):
|
|
|
# 0: 向前移动到T字中心(记录中心点坐标 + 角度校正)
|
|
|
# 1: 左转90度(PID控制)
|
|
|
# 2: 向左移动到左端(角度校正)
|
|
|
# 3: 右转180度(PID控制)
|
|
|
# 4: 向右移动到右端(记录右端点坐标 + 角度校正)
|
|
|
# 5: 右转180度(PID控制)
|
|
|
# 6: 向左移动回中心(动态计算距离 + 角度校正)✅
|
|
|
# 7: 左转90度(PID控制)
|
|
|
# 8: 向下移动回起点(动态计算距离 + 角度校正)✅
|
|
|
# 9: 左转180度并重置(PID控制)
|
|
|
|
|
|
if self.current_segment == 0:
|
|
|
# 阶段0: 向前移动T字高度的距离(竖杠)+ 角度校正
|
|
|
target_distance = self.t_height
|
|
|
target_angle = 0.0 # 保持朝前(0度)
|
|
|
|
|
|
# 计算从起点移动的实际距离
|
|
|
moved_distance = math.sqrt((self.x - 0.0)**2 + (self.y - 0.0)**2)
|
|
|
|
|
|
if moved_distance < target_distance:
|
|
|
twist.linear.x = self.linear_speed
|
|
|
# 添加角度校正
|
|
|
twist.angular.z = self.linear_angle_correction(target_angle)
|
|
|
self.update_position(twist, 0.02)
|
|
|
else:
|
|
|
# 记录T字中心点坐标(第一次到达)
|
|
|
if self.t_center_x is None:
|
|
|
self.t_center_x = self.x
|
|
|
self.t_center_y = self.y
|
|
|
self.get_logger().info(f'记录T字中心点: ({self.x:.4f}, {self.y:.4f})')
|
|
|
# 进入下一阶段
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
elif self.current_segment == 1:
|
|
|
# 阶段1: 左转90度(PID控制)
|
|
|
if not self.pid_active:
|
|
|
# 初始化PID控制
|
|
|
self.target_angle = math.pi / 2
|
|
|
self.reset_pid()
|
|
|
self.pid_active = True
|
|
|
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 检查是否到达目标角度
|
|
|
if abs(error) < self.angle_tolerance:
|
|
|
# 到达目标,停止PID
|
|
|
self.pid_active = False
|
|
|
# 记录阶段2起点
|
|
|
self.segment_start_x = self.x
|
|
|
self.segment_start_y = self.y
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
else:
|
|
|
# 使用PID计算角速度
|
|
|
twist.angular.z = self.pid_angle_control()
|
|
|
self.update_position(twist, 0.02)
|
|
|
|
|
|
elif self.current_segment == 2:
|
|
|
# 阶段2: 向左移动T字宽度的一半(从中心到左端)+ 角度校正
|
|
|
target_distance = self.t_width / 2
|
|
|
target_angle = math.pi / 2 # 保持朝左(90度)
|
|
|
|
|
|
moved_distance = math.sqrt((self.x - self.segment_start_x)**2 + (self.y - self.segment_start_y)**2)
|
|
|
|
|
|
if moved_distance < target_distance:
|
|
|
twist.linear.x = self.linear_speed
|
|
|
# 添加角度校正
|
|
|
twist.angular.z = self.linear_angle_correction(target_angle)
|
|
|
self.update_position(twist, 0.02)
|
|
|
else:
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
elif self.current_segment == 3:
|
|
|
# 阶段3: 右转180度(PID控制)
|
|
|
if not self.pid_active:
|
|
|
# 初始化PID控制
|
|
|
self.target_angle = -math.pi / 2
|
|
|
self.reset_pid()
|
|
|
self.pid_active = True
|
|
|
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 检查是否到达目标角度
|
|
|
if abs(error) < self.angle_tolerance:
|
|
|
# 到达目标,停止PID
|
|
|
self.pid_active = False
|
|
|
# 记录阶段4起点
|
|
|
self.segment_start_x = self.x
|
|
|
self.segment_start_y = self.y
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
else:
|
|
|
# 使用PID计算角速度
|
|
|
twist.angular.z = self.pid_angle_control()
|
|
|
self.update_position(twist, 0.02)
|
|
|
|
|
|
elif self.current_segment == 4:
|
|
|
# 阶段4: 向右移动整个T字宽度(横杠完整长度)+ 角度校正
|
|
|
target_distance = self.t_width
|
|
|
target_angle = -math.pi / 2 # 保持朝右(-90度)
|
|
|
|
|
|
moved_distance = math.sqrt((self.x - self.segment_start_x)**2 + (self.y - self.segment_start_y)**2)
|
|
|
|
|
|
if moved_distance < target_distance:
|
|
|
twist.linear.x = self.linear_speed
|
|
|
# 添加角度校正
|
|
|
twist.angular.z = self.linear_angle_correction(target_angle)
|
|
|
self.update_position(twist, 0.02)
|
|
|
else:
|
|
|
# 记录右端点坐标,用于动态计算返回距离
|
|
|
self.right_end_x = self.x
|
|
|
self.right_end_y = self.y
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
elif self.current_segment == 5:
|
|
|
# 阶段5: 右转180度(PID控制)
|
|
|
if not self.pid_active:
|
|
|
# 初始化PID控制
|
|
|
self.target_angle = math.pi / 2
|
|
|
self.reset_pid()
|
|
|
self.pid_active = True
|
|
|
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 检查是否到达目标角度
|
|
|
if abs(error) < self.angle_tolerance:
|
|
|
# 到达目标,停止PID
|
|
|
self.pid_active = False
|
|
|
# 记录阶段6起点
|
|
|
self.segment_start_x = self.x
|
|
|
self.segment_start_y = self.y
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
else:
|
|
|
# 使用PID计算角速度
|
|
|
twist.angular.z = self.pid_angle_control()
|
|
|
self.update_position(twist, 0.02)
|
|
|
|
|
|
elif self.current_segment == 6:
|
|
|
# 阶段6: 向左移动回到中心(智能动态距离计算 + 角度校正)✅
|
|
|
# 动态计算到T字中心的实际距离
|
|
|
if self.t_center_x is not None and self.right_end_x is not None:
|
|
|
dx = self.t_center_x - self.right_end_x
|
|
|
dy = self.t_center_y - self.right_end_y
|
|
|
target_distance = math.sqrt(dx*dx + dy*dy)
|
|
|
else:
|
|
|
# 备用:使用理论距离
|
|
|
target_distance = self.t_width / 2
|
|
|
|
|
|
target_angle = math.pi / 2 # 保持朝左(90度)
|
|
|
|
|
|
# 基于实际移动距离判断
|
|
|
moved_distance = math.sqrt((self.x - self.segment_start_x)**2 + (self.y - self.segment_start_y)**2)
|
|
|
|
|
|
if moved_distance < target_distance:
|
|
|
twist.linear.x = self.linear_speed
|
|
|
# 添加角度校正
|
|
|
twist.angular.z = self.linear_angle_correction(target_angle)
|
|
|
self.update_position(twist, 0.02)
|
|
|
else:
|
|
|
# 位置自然到达,进入下一阶段
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
elif self.current_segment == 7:
|
|
|
# 阶段7: 左转90度(PID控制)
|
|
|
if not self.pid_active:
|
|
|
# 初始化PID控制
|
|
|
self.target_angle = math.pi
|
|
|
self.reset_pid()
|
|
|
self.pid_active = True
|
|
|
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 检查是否到达目标角度
|
|
|
if abs(error) < self.angle_tolerance:
|
|
|
# 到达目标,停止PID
|
|
|
self.pid_active = False
|
|
|
# 记录阶段8起点
|
|
|
self.segment_start_x = self.x
|
|
|
self.segment_start_y = self.y
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
else:
|
|
|
# 使用PID计算角速度
|
|
|
twist.angular.z = self.pid_angle_control()
|
|
|
self.update_position(twist, 0.02)
|
|
|
|
|
|
elif self.current_segment == 8:
|
|
|
# 阶段8: 向下移动回到起点(智能动态距离计算 + 角度校正)✅
|
|
|
# 初始化起点坐标
|
|
|
if self.start_x is None:
|
|
|
self.start_x = 0.0
|
|
|
self.start_y = 0.0
|
|
|
|
|
|
# 动态计算到起点的实际距离
|
|
|
if self.t_center_x is not None:
|
|
|
dx = self.start_x - self.t_center_x
|
|
|
dy = self.start_y - self.t_center_y
|
|
|
target_distance = math.sqrt(dx*dx + dy*dy)
|
|
|
else:
|
|
|
target_distance = self.t_height
|
|
|
|
|
|
target_angle = math.pi # 保持朝下(180度)
|
|
|
|
|
|
# 基于实际移动距离判断
|
|
|
moved_distance = math.sqrt((self.x - self.segment_start_x)**2 + (self.y - self.segment_start_y)**2)
|
|
|
|
|
|
if moved_distance < target_distance:
|
|
|
twist.linear.x = self.linear_speed
|
|
|
# 添加角度校正
|
|
|
twist.angular.z = self.linear_angle_correction(target_angle)
|
|
|
self.update_position(twist, 0.02)
|
|
|
else:
|
|
|
# 位置自然到达,进入下一阶段
|
|
|
self.current_segment += 1
|
|
|
self.segment_start_time = current_time
|
|
|
|
|
|
elif self.current_segment == 9:
|
|
|
# 阶段9: 左转180度回到初始朝向(PID控制)
|
|
|
if not self.pid_active:
|
|
|
# 初始化PID控制
|
|
|
self.target_angle = 0.0
|
|
|
self.reset_pid()
|
|
|
self.pid_active = True
|
|
|
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 检查是否到达目标角度
|
|
|
if abs(error) < self.angle_tolerance:
|
|
|
# 到达目标,停止PID
|
|
|
self.pid_active = False
|
|
|
|
|
|
# 循环结束:重置到精确起点,确保每轮一致
|
|
|
self.x = 0.0
|
|
|
self.y = 0.0
|
|
|
self.theta = 0.0
|
|
|
# ⚠️ 只重置右端点,T字中心点保持固定(第一次记录后永久使用)
|
|
|
self.right_end_x = None
|
|
|
self.right_end_y = None
|
|
|
|
|
|
if self.loop_enabled:
|
|
|
self.current_segment = 0
|
|
|
self.segment_start_time = current_time
|
|
|
self.get_logger().info('完成一轮T字形轨迹,开始新循环')
|
|
|
else:
|
|
|
self.current_segment = 10
|
|
|
self.get_logger().info('T字形轨迹完成')
|
|
|
else:
|
|
|
# 使用PID计算角速度
|
|
|
twist.angular.z = self.pid_angle_control()
|
|
|
self.update_position(twist, 0.02)
|
|
|
|
|
|
# 发布速度指令到/cmd_vel话题
|
|
|
self.cmd_vel_pub.publish(twist)
|
|
|
|
|
|
def pid_angle_control(self):
|
|
|
"""PID角度控制算法
|
|
|
|
|
|
Returns:
|
|
|
float: 计算出的角速度(弧度/秒)
|
|
|
"""
|
|
|
# 计算角度误差
|
|
|
error = self.target_angle - self.theta
|
|
|
|
|
|
# 归一化误差到[-π, π]范围(处理角度跳变)
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# P: 比例项 - 误差越大,输出越大
|
|
|
p_output = self.pid_kp * error
|
|
|
|
|
|
# I: 积分项 - 消除长期稳态误差
|
|
|
self.error_sum += error
|
|
|
# 限制积分累积,防止积分饱和
|
|
|
max_integral = 10.0
|
|
|
if self.error_sum > max_integral:
|
|
|
self.error_sum = max_integral
|
|
|
elif self.error_sum < -max_integral:
|
|
|
self.error_sum = -max_integral
|
|
|
i_output = self.pid_ki * self.error_sum
|
|
|
|
|
|
# D: 微分项 - 预测趋势,减少超调
|
|
|
error_rate = error - self.last_error
|
|
|
d_output = self.pid_kd * error_rate
|
|
|
|
|
|
# PID总输出
|
|
|
angular_velocity = p_output + i_output + d_output
|
|
|
|
|
|
# 限制最大角速度
|
|
|
if angular_velocity > self.angular_speed:
|
|
|
angular_velocity = self.angular_speed
|
|
|
elif angular_velocity < -self.angular_speed:
|
|
|
angular_velocity = -self.angular_speed
|
|
|
|
|
|
# 保存当前误差用于下次微分计算
|
|
|
self.last_error = error
|
|
|
|
|
|
return angular_velocity
|
|
|
|
|
|
def reset_pid(self):
|
|
|
"""重置PID状态"""
|
|
|
self.error_sum = 0.0
|
|
|
self.last_error = 0.0
|
|
|
|
|
|
def linear_angle_correction(self, target_angle):
|
|
|
"""线性运动角度校正
|
|
|
|
|
|
Args:
|
|
|
target_angle: 目标角度(弧度)
|
|
|
|
|
|
Returns:
|
|
|
float: 校正角速度(弧度/秒)
|
|
|
"""
|
|
|
# 计算角度误差
|
|
|
error = target_angle - self.theta
|
|
|
|
|
|
# 归一化误差到[-π, π]范围
|
|
|
while error > math.pi:
|
|
|
error -= 2 * math.pi
|
|
|
while error < -math.pi:
|
|
|
error += 2 * math.pi
|
|
|
|
|
|
# 如果误差在容差范围内,不需要校正
|
|
|
if abs(error) < self.linear_angle_tolerance:
|
|
|
return 0.0
|
|
|
|
|
|
# 计算校正角速度(比例控制)
|
|
|
correction_velocity = self.angle_correction_speed * (error / abs(error))
|
|
|
|
|
|
# 限制最大校正速度
|
|
|
if correction_velocity > self.angle_correction_speed:
|
|
|
correction_velocity = self.angle_correction_speed
|
|
|
elif correction_velocity < -self.angle_correction_speed:
|
|
|
correction_velocity = -self.angle_correction_speed
|
|
|
|
|
|
return correction_velocity
|
|
|
|
|
|
def update_position(self, twist, dt):
|
|
|
"""更新机器人位置(差速驱动运动学模型)
|
|
|
|
|
|
Args:
|
|
|
twist: 速度指令(线速度和角速度)
|
|
|
dt: 时间步长(秒)
|
|
|
"""
|
|
|
# 根据当前速度和朝向更新位置
|
|
|
self.x += twist.linear.x * math.cos(self.theta) * dt
|
|
|
self.y += twist.linear.x * math.sin(self.theta) * dt
|
|
|
self.theta += twist.angular.z * dt
|
|
|
|
|
|
# 归一化角度到[-π, π]范围
|
|
|
self.theta = math.atan2(math.sin(self.theta), math.cos(self.theta))
|
|
|
|
|
|
def publish_status(self):
|
|
|
"""发布TF变换和路径信息 - 10Hz频率"""
|
|
|
current_time = self.get_clock().now()
|
|
|
|
|
|
# 发布TF变换: odom坐标系 -> base_link坐标系
|
|
|
t = TransformStamped()
|
|
|
t.header.stamp = current_time.to_msg()
|
|
|
t.header.frame_id = 'odom'
|
|
|
t.child_frame_id = 'base_link'
|
|
|
t.transform.translation.x = self.x
|
|
|
t.transform.translation.y = self.y
|
|
|
t.transform.translation.z = 0.0
|
|
|
|
|
|
# 将欧拉角转换为四元数(ROS使用四元数表示旋转)
|
|
|
q = self.euler_to_quaternion(0, 0, self.theta)
|
|
|
t.transform.rotation.x = q[0]
|
|
|
t.transform.rotation.y = q[1]
|
|
|
t.transform.rotation.z = q[2]
|
|
|
t.transform.rotation.w = q[3]
|
|
|
|
|
|
# 广播TF变换
|
|
|
self.tf_broadcaster.sendTransform(t)
|
|
|
|
|
|
# 添加当前位姿到路径
|
|
|
pose = PoseStamped()
|
|
|
pose.header.stamp = current_time.to_msg()
|
|
|
pose.header.frame_id = 'odom'
|
|
|
pose.pose.position.x = self.x
|
|
|
pose.pose.position.y = self.y
|
|
|
pose.pose.position.z = 0.0
|
|
|
pose.pose.orientation.x = q[0]
|
|
|
pose.pose.orientation.y = q[1]
|
|
|
pose.pose.orientation.z = q[2]
|
|
|
pose.pose.orientation.w = q[3]
|
|
|
|
|
|
self.path_msg.poses.append(pose)
|
|
|
self.path_msg.header.stamp = current_time.to_msg()
|
|
|
|
|
|
# 限制路径长度,避免内存无限增长(保留最近2000个点)
|
|
|
if len(self.path_msg.poses) > 2000:
|
|
|
self.path_msg.poses.pop(0)
|
|
|
|
|
|
# 发布路径到/robot_path话题(RViz中显示为绿色轨迹)
|
|
|
self.path_pub.publish(self.path_msg)
|
|
|
|
|
|
def publish_t_shape_marker(self):
|
|
|
"""发布T字形轨迹标记(参考线)"""
|
|
|
marker = Marker()
|
|
|
marker.header.frame_id = 'odom'
|
|
|
marker.header.stamp = self.get_clock().now().to_msg()
|
|
|
marker.ns = 't_shape'
|
|
|
marker.id = 0
|
|
|
marker.type = Marker.LINE_STRIP
|
|
|
marker.action = Marker.ADD
|
|
|
marker.scale.x = 0.05
|
|
|
marker.color.r = 1.0
|
|
|
marker.color.g = 0.0
|
|
|
marker.color.b = 0.0
|
|
|
marker.color.a = 0.5
|
|
|
|
|
|
# 定义T字形路径点
|
|
|
from geometry_msgs.msg import Point
|
|
|
|
|
|
p1 = Point()
|
|
|
p1.x, p1.y, p1.z = 0.0, 0.0, 0.0
|
|
|
|
|
|
p2 = Point()
|
|
|
p2.x, p2.y, p2.z = self.t_height, 0.0, 0.0
|
|
|
|
|
|
p3 = Point()
|
|
|
p3.x, p3.y, p3.z = self.t_height, self.t_width / 2, 0.0
|
|
|
|
|
|
p4 = Point()
|
|
|
p4.x, p4.y, p4.z = self.t_height, 0.0, 0.0
|
|
|
|
|
|
p5 = Point()
|
|
|
p5.x, p5.y, p5.z = self.t_height, -self.t_width / 2, 0.0
|
|
|
|
|
|
p6 = Point()
|
|
|
p6.x, p6.y, p6.z = self.t_height, 0.0, 0.0
|
|
|
|
|
|
p7 = Point()
|
|
|
p7.x, p7.y, p7.z = 0.0, 0.0, 0.0
|
|
|
|
|
|
marker.points = [p1, p2, p3, p4, p5, p6, p7]
|
|
|
|
|
|
self.marker_pub.publish(marker)
|
|
|
self.create_timer(5.0, lambda: self.marker_pub.publish(marker))
|
|
|
|
|
|
def euler_to_quaternion(self, roll, pitch, yaw):
|
|
|
"""将欧拉角转换为四元数"""
|
|
|
qx = math.sin(roll/2) * math.cos(pitch/2) * math.cos(yaw/2) - math.cos(roll/2) * math.sin(pitch/2) * math.sin(yaw/2)
|
|
|
qy = math.cos(roll/2) * math.sin(pitch/2) * math.cos(yaw/2) + math.sin(roll/2) * math.cos(pitch/2) * math.sin(yaw/2)
|
|
|
qz = math.cos(roll/2) * math.cos(pitch/2) * math.sin(yaw/2) - math.sin(roll/2) * math.sin(pitch/2) * math.cos(yaw/2)
|
|
|
qw = math.cos(roll/2) * math.cos(pitch/2) * math.cos(yaw/2) + math.sin(roll/2) * math.sin(pitch/2) * math.sin(yaw/2)
|
|
|
return [qx, qy, qz, qw]
|
|
|
|
|
|
|
|
|
def main(args=None):
|
|
|
rclpy.init(args=args)
|
|
|
node = TShapeController()
|
|
|
|
|
|
try:
|
|
|
rclpy.spin(node)
|
|
|
except KeyboardInterrupt:
|
|
|
pass
|
|
|
finally:
|
|
|
node.destroy_node()
|
|
|
rclpy.shutdown()
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
main() |