Initial Commit

This commit is contained in:
2026-02-09 03:39:49 +00:00
commit a756be4bf7
71 changed files with 6705 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""Waypoint Follower - Follows relative waypoints without GPS."""
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped, Twist
from nav_msgs.msg import Odometry, Path
from std_msgs.msg import Int32, String, Bool
import numpy as np
from enum import Enum
class WaypointState(Enum):
IDLE = 0
NAVIGATING = 1
REACHED = 2
PAUSED = 3
class WaypointFollower(Node):
def __init__(self):
super().__init__('waypoint_follower')
self.declare_parameter('waypoint_radius', 0.5)
self.declare_parameter('max_velocity', 2.0)
self.declare_parameter('kp_linear', 0.8)
self.declare_parameter('kp_angular', 1.0)
self.waypoint_radius = self.get_parameter('waypoint_radius').value
self.max_velocity = self.get_parameter('max_velocity').value
self.kp_linear = self.get_parameter('kp_linear').value
self.kp_angular = self.get_parameter('kp_angular').value
self.waypoints = []
self.current_idx = 0
self.state = WaypointState.IDLE
self.current_pose = None
self.odom_sub = self.create_subscription(
Odometry, '/uav/local_position/odom', self.odom_callback, 10)
self.waypoint_sub = self.create_subscription(
PoseStamped, '/waypoint_follower/add_waypoint', self.add_waypoint_callback, 10)
self.path_sub = self.create_subscription(
Path, '/waypoint_follower/set_path', self.set_path_callback, 10)
self.command_sub = self.create_subscription(
String, '/waypoint_follower/command', self.command_callback, 10)
self.cmd_vel_pub = self.create_publisher(Twist, '/uav/cmd_vel', 10)
self.current_waypoint_pub = self.create_publisher(Int32, '/waypoint_follower/current_index', 10)
self.status_pub = self.create_publisher(String, '/waypoint_follower/status', 10)
self.reached_pub = self.create_publisher(Bool, '/waypoint_follower/waypoint_reached', 10)
self.timer = self.create_timer(0.05, self.control_loop)
self.get_logger().info('Waypoint Follower Started - Using LOCAL coordinates only')
def odom_callback(self, msg):
self.current_pose = msg.pose.pose
def add_waypoint_callback(self, msg):
waypoint = np.array([
msg.pose.position.x,
msg.pose.position.y,
msg.pose.position.z
])
self.waypoints.append(waypoint)
self.get_logger().info(f'Added waypoint {len(self.waypoints)}: {waypoint}')
def set_path_callback(self, msg):
self.waypoints = []
for pose_stamped in msg.poses:
waypoint = np.array([
pose_stamped.pose.position.x,
pose_stamped.pose.position.y,
pose_stamped.pose.position.z
])
self.waypoints.append(waypoint)
self.current_idx = 0
self.get_logger().info(f'Set path with {len(self.waypoints)} waypoints')
def command_callback(self, msg):
cmd = msg.data.lower()
if cmd == 'start':
if len(self.waypoints) > 0:
self.state = WaypointState.NAVIGATING
self.get_logger().info('Starting waypoint navigation')
elif cmd == 'pause':
self.state = WaypointState.PAUSED
self.stop()
elif cmd == 'resume':
self.state = WaypointState.NAVIGATING
elif cmd == 'stop':
self.state = WaypointState.IDLE
self.stop()
elif cmd == 'clear':
self.waypoints = []
self.current_idx = 0
self.state = WaypointState.IDLE
elif cmd == 'next':
if self.current_idx < len(self.waypoints) - 1:
self.current_idx += 1
elif cmd == 'prev':
if self.current_idx > 0:
self.current_idx -= 1
def control_loop(self):
self.publish_status()
if self.state != WaypointState.NAVIGATING:
return
if self.current_pose is None or len(self.waypoints) == 0:
return
if self.current_idx >= len(self.waypoints):
self.state = WaypointState.IDLE
self.stop()
self.get_logger().info('All waypoints reached!')
return
current_pos = np.array([
self.current_pose.position.x,
self.current_pose.position.y,
self.current_pose.position.z
])
target = self.waypoints[self.current_idx]
error = target - current_pos
distance = np.linalg.norm(error)
if distance < self.waypoint_radius:
reached_msg = Bool()
reached_msg.data = True
self.reached_pub.publish(reached_msg)
self.get_logger().info(f'Reached waypoint {self.current_idx + 1}/{len(self.waypoints)}')
self.current_idx += 1
return
direction = error / distance
speed = min(self.kp_linear * distance, self.max_velocity)
velocity = direction * speed
cmd = Twist()
cmd.linear.x = float(velocity[0])
cmd.linear.y = float(velocity[1])
cmd.linear.z = float(velocity[2])
self.cmd_vel_pub.publish(cmd)
def stop(self):
cmd = Twist()
self.cmd_vel_pub.publish(cmd)
def publish_status(self):
idx_msg = Int32()
idx_msg.data = self.current_idx
self.current_waypoint_pub.publish(idx_msg)
status_msg = String()
status_msg.data = f'{self.state.name}|{self.current_idx}/{len(self.waypoints)}'
self.status_pub.publish(status_msg)
def main(args=None):
rclpy.init(args=args)
node = WaypointFollower()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()