Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

436 task guidance node for autopilot #502

Open
wants to merge 54 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
917d5bf
Modified guidance algorithim
Oct 9, 2024
cd4d076
Automated autoyapf fixes
invalid-email-address Oct 9, 2024
7845070
added guidance action server
Oct 11, 2024
7157c5c
Resolved merge conflict in guidance.py
Oct 11, 2024
d7dd424
Automated autoyapf fixes
invalid-email-address Oct 11, 2024
cb2e138
Remove unwanted folder
Oct 11, 2024
0e8585a
Merge remote-tracking branch 'origin/436-task-guidance-node-for-autop…
Oct 11, 2024
6ab5edf
Develop (#487)
kluge7 Oct 13, 2024
cd7f490
Fix errors of no executable
Oct 13, 2024
29c1ad3
trying to resolve merge
Oct 13, 2024
07261ee
Automated autoyapf fixes
invalid-email-address Oct 13, 2024
1fb8b90
Made changes to guidance_action_server and guidance_los files
Oct 21, 2024
d694e89
update guidance action server
Oct 21, 2024
a007e80
Automated autoyapf fixes
invalid-email-address Oct 21, 2024
05ff163
Delete unnecessary files.
Oct 21, 2024
f44e105
modification of the guidance
Oct 23, 2024
9ca5b31
Fixed auto-pilot
Oct 27, 2024
15fed69
Automated autoyapf fixes
invalid-email-address Oct 27, 2024
46455e6
intermediate commit
Nov 3, 2024
9e7d7ba
Merge remote-tracking branch 'origin/485-task-auv-gui' into 436-task-…
Nov 3, 2024
fc4780a
feat: create guidance action server
Nov 10, 2024
79a48b2
Merge remote-tracking branch 'origin/develop' into 436-task-guidance-…
Nov 10, 2024
acc38cb
Merge remote-tracking branch 'origin/develop' into 436-task-guidance-…
Nov 10, 2024
bc076c6
fix(gui): resolve merge conflict in auv_gui.py, retain local updates
kluge7 Nov 11, 2024
9b49170
Modified LOS guidance action server with proper comments and doc strings
Nov 16, 2024
2e9e1c8
Added comprehensive docstrings and comments to LOS guidance algorithm
Nov 16, 2024
d1987fa
change LOS guidance to use third order low-pass filter
Nov 17, 2024
ab21880
fix: vortex_msgs develop branch is target
Hallfred Nov 17, 2024
fa599fe
fix: added pyqt install script
Hallfred Nov 17, 2024
b695f1d
fix: correct ref
Hallfred Nov 17, 2024
03bb8d3
feat: removed tests
Hallfred Nov 17, 2024
992487d
refactor: applied formatting changes
Nov 17, 2024
a4700f8
add stuff
Nov 17, 2024
6ad96af
added stuff
Nov 17, 2024
a78dc87
added stuff
Nov 17, 2024
1bfc2ee
Fixed filter and remove tuple
Nov 17, 2024
e614471
fix CMaklists
Nov 17, 2024
d0061dc
Modified package.xml
Nov 17, 2024
aa27f9f
remove unnecessary comments and added a readme.md file and a yaml fil…
Dec 22, 2024
fd6db80
Apply pre-commit hook fixes
Dec 26, 2024
2f4cfa9
Modified guidance action server node.
Dec 28, 2024
2d4cadf
modify action server and launch file
Jan 2, 2025
8ba17ba
working guidance action server
Jan 7, 2025
3160f92
added goal callback
Jan 7, 2025
afb0a89
added cancel request
Jan 7, 2025
845e362
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 7, 2025
62b1fd8
refactor guidance action server
Jan 7, 2025
3b5c8d9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 7, 2025
f748524
feat: remove dependency on transforms3d
Andeshog Jan 8, 2025
87309ba
refactor: remove try-except block
Andeshog Jan 8, 2025
13d1e6f
refactor: correctly initialize new commands after new goal request
Andeshog Jan 8, 2025
e6e3e9f
modified guidance algorithm
Jan 8, 2025
927b03a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2025
3b80ce0
[pre-commit.ci] auto fixes from pre-commit.com hooks
Hallfred Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions guidance/guidance_los/guidance_los/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from guidance_los.los_guidance_algorithm import FirstOrderLOSGuidance
from guidance_los.los_guidance_action_server import GuidanceActionServer
263 changes: 263 additions & 0 deletions guidance/guidance_los/guidance_los/los_guidance_action_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# los_guidance_action_server.py
# location: vortex-auv/guidance/guidance_los/guidance_los/los_guidance_action_server.py

#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from vortex_msgs.action import NavigateWaypoints
from vortex_msgs.msg import LOSGuidance
from geometry_msgs.msg import PoseStamped, Vector3Stamped
from nav_msgs.msg import Odometry
from transforms3d.euler import quat2euler
import numpy as np
import time
from std_msgs.msg import String
from guidance_los.los_guidance_algorithm import FirstOrderLOSGuidance
from std_msgs.msg import Float32MultiArray, MultiArrayDimension

class GuidanceActionServer(Node):
def __init__(self):
super().__init__('guidance_action_server')

# Initialize guidance calculator
self.guidance_calculator = FirstOrderLOSGuidance()

# Publishers
self.output_pub = self.create_publisher(LOSGuidance, '/guidance/los', 10)
self.ref_pub = self.create_publisher(PoseStamped, '/guidance/reference', 10)
self.error_pub = self.create_publisher(Vector3Stamped, '/guidance/errors', 10)

# Debug publisher
self.log_publisher = self.create_publisher(String, '/guidance_action_server/logs', 10)

# Subscriber
self.create_subscription(Odometry, '/nucleus/odom', self.odom_callback, 10)

# Add this line - waypoint list publisher
self.waypoint_list_pub = self.create_publisher(Float32MultiArray, '/guidance/waypoint_list', 10)
AbubakarAliyuBadawi marked this conversation as resolved.
Show resolved Hide resolved

# Action server
self._action_server = ActionServer(
self,
NavigateWaypoints,
'navigate_waypoints',
self.execute_callback
)
AbubakarAliyuBadawi marked this conversation as resolved.
Show resolved Hide resolved

# State variables
self.current_position = None # [x, y, z, yaw, pitch]
AbubakarAliyuBadawi marked this conversation as resolved.
Show resolved Hide resolved
self.waypoints = []
self.current_waypoint_index = 0
self.goal_handle = None
self.update_period = 0.1

self.get_logger().info('Guidance Action Server initialized')

# Move these methods inside the class with proper indentation
def publish_log(self, message: str):
"""Publish debug log message."""
msg = String()
msg.data = message
self.log_publisher.publish(msg)
self.get_logger().info(message)
AbubakarAliyuBadawi marked this conversation as resolved.
Show resolved Hide resolved

def publish_waypoint_list(self):
"""Publish waypoint list for GUI visualization."""
msg = Float32MultiArray()
waypoint_data = []
for wp in self.waypoints:
waypoint_data.extend(wp.tolist())
msg.data = waypoint_data

msg.layout.dim = [
MultiArrayDimension(label="waypoints", size=len(self.waypoints)),
MultiArrayDimension(label="coordinates", size=3)
]

self.waypoint_list_pub.publish(msg)

def odom_callback(self, msg: Odometry):
"""Process odometry data and update guidance if active."""
try:
# Extract orientation
orientation_q = msg.pose.pose.orientation
roll, pitch, yaw = quat2euler([orientation_q.w, orientation_q.x,
orientation_q.y, orientation_q.z])

# Initialize filter on first message
if self.current_position is None:
initial_commands = np.array([0.0, 0.0, yaw])
self.guidance_calculator.reset_filter_state(initial_commands)

# Update current position
self.current_position = np.array([
msg.pose.pose.position.x,
msg.pose.pose.position.y,
msg.pose.pose.position.z,
yaw,
pitch
])
AbubakarAliyuBadawi marked this conversation as resolved.
Show resolved Hide resolved

# Log current position
self.publish_log(
f"Position: x={self.current_position[0]:.3f}, "
f"y={self.current_position[1]:.3f}, "
f"z={self.current_position[2]:.3f}"
)
self.publish_log(
f"Orientation: roll={roll:.3f}, pitch={pitch:.3f}, yaw={yaw:.3f}"
)

# Process guidance if active goal exists
if self.goal_handle is not None and self.goal_handle.is_active:
if len(self.waypoints) > 0:
self.process_guidance()

except Exception as e:
self.get_logger().error(f'Error in odom_callback: {str(e)}')

def process_guidance(self):
"""Process guidance calculations and publish commands."""

self.publish_log(f"Processing guidance for target: {target_point}")
self.publish_log(f"Current position: {self.current_position}")
try:
if self.current_waypoint_index >= len(self.waypoints):
return

target_point = self.waypoints[self.current_waypoint_index]


# Compute guidance commands
commands, distance, depth_error = self.guidance_calculator.compute_guidance(
self.current_position,
target_point,
self.update_period
)

# Publish commands and status
self.publish_guidance(commands)
self.publish_reference(target_point)
self.publish_errors(target_point, depth_error)

# Log guidance status
self.publish_log(
f"Guidance Status: surge={commands[0]:.2f}, "
f"pitch={commands[1]:.2f}, yaw={commands[2]:.2f}"
)
self.publish_log(f"Distance to target: {distance:.2f}")

# Check if waypoint is reached
if distance < 0.5: # 0.5m threshold
self.publish_log(f'Reached waypoint {self.current_waypoint_index}')

# Reset filter state
initial_commands = np.array([0.0, 0.0, self.current_position[3]])
self.guidance_calculator.reset_filter_state(initial_commands)

self.current_waypoint_index += 1

# If all waypoints reached, succeed the goal
if self.current_waypoint_index >= len(self.waypoints):
if self.goal_handle and self.goal_handle.is_active:
self.goal_handle.succeed()
self.goal_handle = None
return

# Publish feedback
if self.goal_handle and self.goal_handle.is_active:
feedback_msg = NavigateWaypoints.Feedback()
feedback_msg.current_waypoint_index = float(self.current_waypoint_index)
self.goal_handle.publish_feedback(feedback_msg)

except Exception as e:
self.get_logger().error(f'Error in process_guidance: {str(e)}')

def execute_callback(self, goal_handle):
"""Execute the waypoint navigation action."""
try:
self.publish_log('Executing waypoint navigation...')

# Store goal handle and reset index
self.goal_handle = goal_handle
self.current_waypoint_index = 0

# Extract waypoints
self.waypoints = [
np.array([
wp.pose.position.x,
wp.pose.position.y,
wp.pose.position.z
]) for wp in goal_handle.request.waypoints
]

# Add this line to publish waypoints for GUI
self.publish_waypoint_list()

self.publish_log(f'Received {len(self.waypoints)} waypoints')

# Wait for completion or cancellation
while rclpy.ok():
if not goal_handle.is_active:
return NavigateWaypoints.Result(success=False)

if goal_handle.is_cancel_requested:
self.publish_log('Goal canceled')
goal_handle.canceled()
self.goal_handle = None
return NavigateWaypoints.Result(success=False)

if self.current_waypoint_index >= len(self.waypoints):
self.publish_log('All waypoints reached')
return NavigateWaypoints.Result(success=True)

time.sleep(0.1)

except Exception as e:
self.get_logger().error(f'Error in execute_callback: {str(e)}')
return NavigateWaypoints.Result(success=False)

def publish_guidance(self, commands):
"""Publish guidance commands."""
msg = LOSGuidance()
msg.surge = commands[0]
msg.pitch = commands[1]
msg.yaw = commands[2]
self.output_pub.publish(msg)

def publish_reference(self, target):
"""Publish reference pose."""
msg = PoseStamped()
msg.header.frame_id = "odom"
msg.header.stamp = self.get_clock().now().to_msg()
msg.pose.position.x = target[0]
msg.pose.position.y = target[1]
msg.pose.position.z = target[2]
self.ref_pub.publish(msg)

def publish_errors(self, target, depth_error):
"""Publish position errors."""
msg = Vector3Stamped()
msg.header.frame_id = "odom"
msg.header.stamp = self.get_clock().now().to_msg()
msg.vector.x = target[0] - self.current_position[0]
msg.vector.y = target[1] - self.current_position[1]
msg.vector.z = depth_error
self.error_pub.publish(msg)

def main(args=None):
rclpy.init(args=args)
action_server = GuidanceActionServer()

try:
rclpy.spin(action_server)
except Exception as e:
action_server.get_logger().error(f'Error: {str(e)}')
finally:
action_server.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()
Loading
Loading