1
0
forked from zbw/yiliao2026

目前实现较快速从原点到达某个点,但是然后回到原点时有较大问题

This commit is contained in:
2026-07-22 17:37:07 +08:00
parent 9b06d149d5
commit 90761b40b2
49 changed files with 120143 additions and 63 deletions

187
src/planner/scripts/topology_pure_pursuit_node.py Normal file → Executable file
View File

@@ -10,7 +10,7 @@ from geometry_msgs.msg import PoseStamped, Twist
from nav_msgs.msg import Odometry, Path as NavPath
from rclpy.node import Node
from rclpy.time import Time
from std_msgs.msg import String
from std_msgs.msg import Bool, String
import yaml
from rclpy.duration import Duration
@@ -53,6 +53,7 @@ class TopologyPurePursuit(Node):
self.declare_parameter("plan_topic", "/plan")
self.declare_parameter("cmd_vel_topic", "/cmd_vel")
self.declare_parameter("status_topic", "/topology_status")
self.declare_parameter("cancel_topic", "/navigation_cancel")
self.declare_parameter("enable_topology_planning", True)
self.declare_parameter("accept_external_plan", False)
self.declare_parameter("publish_cmd_vel", True)
@@ -69,11 +70,16 @@ class TopologyPurePursuit(Node):
self.declare_parameter("curvature_slowdown_gain", 0.18)
self.declare_parameter("max_angular_speed", 1.5)
self.declare_parameter("min_turning_radius", 0.35)
self.declare_parameter("goal_yaw_tolerance", 0.10)
self.declare_parameter("align_goal_yaw", True)
self.declare_parameter("final_alignment_offset", 0.25)
self.declare_parameter("final_alignment_speed", 0.10)
self.declare_parameter("latency_compensation", True)
self.declare_parameter("max_latency_compensation", 0.25)
self.declare_parameter("max_odom_age", 0.80)
self.declare_parameter("tf_timeout", 0.05)
self.declare_parameter("stop_without_plan", True)
self.declare_parameter("state_log_period", 1.0)
self.map_frame = self.get_parameter("map_frame").value
self.enable_topology_planning = bool(
@@ -107,6 +113,16 @@ class TopologyPurePursuit(Node):
self.min_turning_radius = float(
self.get_parameter("min_turning_radius").value
)
self.goal_yaw_tolerance = float(
self.get_parameter("goal_yaw_tolerance").value
)
self.align_goal_yaw = bool(self.get_parameter("align_goal_yaw").value)
self.final_alignment_offset = max(
0.05, float(self.get_parameter("final_alignment_offset").value)
)
self.final_alignment_speed = max(
self.min_linear_speed, float(self.get_parameter("final_alignment_speed").value)
)
self.latency_compensation = bool(
self.get_parameter("latency_compensation").value
)
@@ -116,6 +132,9 @@ class TopologyPurePursuit(Node):
self.max_odom_age = float(self.get_parameter("max_odom_age").value)
self.tf_timeout = float(self.get_parameter("tf_timeout").value)
self.stop_without_plan = bool(self.get_parameter("stop_without_plan").value)
self.state_log_period = max(
0.0, float(self.get_parameter("state_log_period").value)
)
self.nodes = {}
self.edges = {}
@@ -139,6 +158,9 @@ class TopologyPurePursuit(Node):
self.completed = True
self.tried_default_goal = False
self.last_tf_warn_ns = 0
self.last_state_log_ns = 0
self.last_cmd_linear = 0.0
self.last_cmd_angular = 0.0
self.plan_pub = self.create_publisher(
NavPath, self.get_parameter("plan_topic").value, 10
@@ -156,6 +178,12 @@ class TopologyPurePursuit(Node):
self._odom_callback,
10,
)
self.cancel_sub = self.create_subscription(
Bool,
self.get_parameter("cancel_topic").value,
self._cancel_callback,
10,
)
self.goal_pose_sub = None
self.goal_node_sub = None
self.external_plan_sub = None
@@ -183,7 +211,9 @@ class TopologyPurePursuit(Node):
period = 1.0 / max(1.0, float(self.get_parameter("control_rate").value))
self.timer = self.create_timer(period, self._control_loop)
self._publish_status(
f"ready graph_nodes={len(self.nodes)} graph_edges={sum(len(v) for v in self.edges.values())}"
f"ready graph_nodes={len(self.nodes)} "
f"graph_edges={sum(len(v) for v in self.edges.values())} "
f"motion_enabled={self.publish_cmd_vel}"
)
def _load_graph(self, graph_file_value):
@@ -295,7 +325,23 @@ class TopologyPurePursuit(Node):
self.active_goal_node = "external_plan"
self.current_path_index = 0
self.completed = False
self._publish_status(f"external_plan_ok points={len(self.active_path)}")
path_length = self._path_length(self.active_path)
goal_x, goal_y, goal_yaw = self.active_path[-1]
self._publish_status(
f"external_plan_ok points={len(self.active_path)} "
f"length={path_length:.3f} goal=({goal_x:.3f},{goal_y:.3f},{goal_yaw:.3f})"
)
def _cancel_callback(self, msg):
if not msg.data:
return
self.active_path = []
self.active_node_path = []
self.active_goal_node = ""
self.current_path_index = 0
self.completed = True
self._publish_cmd(0.0, 0.0)
self._publish_status("navigation_cancelled")
def plan_to_node(self, goal_node):
if goal_node not in self.nodes:
@@ -496,31 +542,57 @@ class TopologyPurePursuit(Node):
def _control_loop(self):
if not self.publish_cmd_vel:
pose = self._current_pose(compensate=False)
state_args = {"pose": pose, "mode": "motion_disabled"}
if pose is not None and self.active_path:
x, y, yaw, _, _ = pose
goal_x, goal_y, goal_yaw = self.active_path[-1]
state_args["goal_distance"] = math.hypot(goal_x - x, goal_y - y)
state_args["goal_yaw_error"] = abs(normalize_angle(goal_yaw - yaw))
nearest_index = self._nearest_path_index(x, y)
nearest_x, nearest_y, _ = self.active_path[nearest_index]
state_args["nearest_index"] = nearest_index
state_args["cross_track_error"] = math.hypot(nearest_x - x, nearest_y - y)
self._maybe_log_state(**state_args)
return
pose = self._current_pose(compensate=True)
if pose is None:
self._stop_if_needed("stop no_recent_odom")
self._maybe_log_state(pose=None, mode="no_recent_odom")
return
if self.completed or len(self.active_path) < 2:
if self.stop_without_plan:
self._publish_cmd(0.0, 0.0)
self._maybe_log_state(pose=pose, mode="idle")
return
x, y, yaw, _, _ = pose
goal_x, goal_y, _ = self.active_path[-1]
goal_yaw = self.active_path[-1][2]
goal_distance = math.hypot(goal_x - x, goal_y - y)
goal_yaw_error = abs(normalize_angle(goal_yaw - yaw))
if goal_distance <= self.goal_tolerance:
if self.align_goal_yaw and goal_yaw_error > self.goal_yaw_tolerance:
self._align_final_yaw(
x, y, yaw, goal_x, goal_y, goal_yaw, goal_yaw_error
)
return
self.completed = True
self._publish_cmd(0.0, 0.0)
self._publish_status(f"goal_reached {self.active_goal_node}")
self._publish_status(
f"goal_reached {self.active_goal_node} "
f"goal_error={goal_distance:.3f} yaw_error={goal_yaw_error:.3f}"
)
return
nearest_index = self._nearest_path_index(x, y)
self.current_path_index = nearest_index
target_index = self._lookahead_index(x, y, nearest_index)
target_x, target_y, target_path_yaw = self.active_path[target_index]
nearest_x, nearest_y, _ = self.active_path[nearest_index]
cross_track_error = math.hypot(nearest_x - x, nearest_y - y)
dx = target_x - x
dy = target_y - y
@@ -540,6 +612,51 @@ class TopologyPurePursuit(Node):
angular = speed * curvature
angular = max(-self.max_angular_speed, min(self.max_angular_speed, angular))
self._publish_cmd(speed, angular)
self._maybe_log_state(
pose=pose,
mode="tracking",
goal_distance=goal_distance,
goal_yaw_error=goal_yaw_error,
nearest_index=nearest_index,
target_index=target_index,
cross_track_error=cross_track_error,
reverse=reverse,
curvature=curvature,
lookahead=lookahead,
)
def _align_final_yaw(self, x, y, yaw, goal_x, goal_y, goal_yaw, goal_yaw_error):
reverse = math.cos(normalize_angle(goal_yaw - yaw)) < 0.0
target_x = goal_x - self.final_alignment_offset * math.cos(goal_yaw) if reverse else goal_x + self.final_alignment_offset * math.cos(goal_yaw)
target_y = goal_y - self.final_alignment_offset * math.sin(goal_yaw) if reverse else goal_y + self.final_alignment_offset * math.sin(goal_yaw)
control_yaw = normalize_angle(yaw + math.pi) if reverse else yaw
dx = target_x - x
dy = target_y - y
local_x = math.cos(control_yaw) * dx + math.sin(control_yaw) * dy
local_y = -math.sin(control_yaw) * dx + math.cos(control_yaw) * dy
lookahead = max(0.05, math.hypot(local_x, local_y))
curvature = 2.0 * local_y / (lookahead * lookahead)
if self.min_turning_radius > 1e-3:
max_curvature = 1.0 / self.min_turning_radius
curvature = max(-max_curvature, min(max_curvature, curvature))
speed = -self.final_alignment_speed if reverse else self.final_alignment_speed
angular = speed * curvature
angular = max(-self.max_angular_speed, min(self.max_angular_speed, angular))
self._publish_cmd(speed, angular)
self._maybe_log_state(
pose=(x, y, yaw, 0.0, 0.0),
mode="align_goal_yaw",
goal_distance=math.hypot(goal_x - x, goal_y - y),
goal_yaw_error=goal_yaw_error,
nearest_index=self.current_path_index,
target_index=len(self.active_path) - 1 if self.active_path else 0,
reverse=reverse,
curvature=curvature,
lookahead=lookahead,
)
self._publish_status(
f"aligning_goal_yaw error={goal_yaw_error:.3f} reverse={reverse}"
)
def _nearest_path_index(self, x, y):
start = max(0, self.current_path_index - 5)
@@ -583,6 +700,62 @@ class TopologyPurePursuit(Node):
return fallback_yaw
return math.atan2(ty - sy, tx - sx)
@staticmethod
def _path_length(path):
return sum(
math.hypot(b[0] - a[0], b[1] - a[1])
for a, b in zip(path, path[1:])
)
def _maybe_log_state(
self,
pose=None,
mode="idle",
goal_distance=None,
goal_yaw_error=None,
nearest_index=None,
target_index=None,
cross_track_error=None,
reverse=False,
curvature=None,
lookahead=None,
):
if self.state_log_period <= 0.0:
return
now_ns = self.get_clock().now().nanoseconds
min_period_ns = int(self.state_log_period * 1_000_000_000)
if now_ns - self.last_state_log_ns < min_period_ns:
return
self.last_state_log_ns = now_ns
parts = [f"tracker_state mode={mode}"]
if pose is None:
parts.append("pose=unavailable")
else:
x, y, yaw, vx, wz = pose
parts.append(f"pose=({x:.3f},{y:.3f},{yaw:.3f})")
parts.append(f"odom_twist=({vx:.3f},{wz:.3f})")
parts.append(f"path_points={len(self.active_path)}")
parts.append(f"completed={self.completed}")
if goal_distance is not None:
parts.append(f"goal_distance={goal_distance:.3f}")
if goal_yaw_error is not None:
parts.append(f"yaw_error={goal_yaw_error:.3f}")
if nearest_index is not None:
parts.append(f"nearest_index={nearest_index}")
if target_index is not None:
parts.append(f"target_index={target_index}")
if cross_track_error is not None:
parts.append(f"cross_track={cross_track_error:.3f}")
if curvature is not None:
parts.append(f"curvature={curvature:.3f}")
if lookahead is not None:
parts.append(f"lookahead={lookahead:.3f}")
parts.append(f"reverse={reverse}")
parts.append(f"cmd=({self.last_cmd_linear:.3f},{self.last_cmd_angular:.3f})")
self.get_logger().info(" ".join(parts))
def _target_speed(self, abs_curvature, goal_distance, reverse):
base_speed = self.reverse_speed if reverse else self.linear_speed
speed = base_speed
@@ -594,9 +767,11 @@ class TopologyPurePursuit(Node):
return -speed if reverse else speed
def _publish_cmd(self, linear, angular):
self.last_cmd_linear = float(linear)
self.last_cmd_angular = float(angular)
cmd = Twist()
cmd.linear.x = float(linear)
cmd.angular.z = float(angular)
cmd.linear.x = self.last_cmd_linear
cmd.angular.z = self.last_cmd_angular
self.cmd_pub.publish(cmd)
def _stop_if_needed(self, reason):