Skip to content

Commit 16acc18

Browse files
Getting closer to a perfect LOS Guidance
1 parent 29929d0 commit 16acc18

File tree

2 files changed

+105
-47
lines changed

2 files changed

+105
-47
lines changed

guidance/guidance_los/guidance_los/los_guidance_algorithm.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __sub__(self, other: "State") -> "State":
3232

3333
def as_los_array(self):
3434
return np.array([self.surge_vel, self.pitch, self.yaw])
35-
35+
3636
def as_pos_array(self):
3737
return np.array([self.x, self.y, self.z])
3838

@@ -212,46 +212,71 @@ def compute_pi_h(current_waypoint: State, next_waypoint: State) -> float:
212212
dx = next_waypoint.x - current_waypoint.x
213213
dy = next_waypoint.y - current_waypoint.y
214214
return np.arctan2(dy, dx)
215-
215+
216216
@staticmethod
217217
def compute_pi_v(current_waypoint: State, next_waypoint: State) -> float:
218218
dz = next_waypoint.z - current_waypoint.z
219-
horizontal_distance = np.sqrt((next_waypoint.x - current_waypoint.x)**2 + (next_waypoint.y - current_waypoint.y)**2)
219+
horizontal_distance = np.sqrt(
220+
(next_waypoint.x - current_waypoint.x) ** 2
221+
+ (next_waypoint.y - current_waypoint.y) ** 2
222+
)
220223
return np.arctan2(-dz, horizontal_distance)
221-
224+
222225
@staticmethod
223226
def compute_rotation_y(pi_v: float) -> np.ndarray:
224-
return np.array([
225-
[np.cos(pi_v), 0, np.sin(pi_v)],
226-
[0, 1, 0],
227-
[-np.sin(pi_v), 0, np.cos(pi_v)]
228-
])
229-
227+
return np.array(
228+
[
229+
[np.cos(pi_v), 0, np.sin(pi_v)],
230+
[0, 1, 0],
231+
[-np.sin(pi_v), 0, np.cos(pi_v)],
232+
]
233+
)
234+
230235
@staticmethod
231236
def compute_rotation_z(pi_h: float) -> np.ndarray:
232-
return np.array([
233-
[np.cos(pi_h), -np.sin(pi_h), 0],
234-
[np.sin(pi_h), np.cos(pi_h), 0],
235-
[0, 0, 1]
236-
])
237-
238-
def compute_psi_d(self, current_waypoint: State, next_waypoint: State, crosstrack_y: float, beta_c: float) -> float:
237+
return np.array(
238+
[
239+
[np.cos(pi_h), -np.sin(pi_h), 0],
240+
[np.sin(pi_h), np.cos(pi_h), 0],
241+
[0, 0, 1],
242+
]
243+
)
244+
245+
def compute_psi_d(
246+
self,
247+
current_waypoint: State,
248+
next_waypoint: State,
249+
crosstrack_y: float,
250+
beta_c: float,
251+
) -> float:
239252
pi_h = self.compute_pi_h(current_waypoint, next_waypoint)
240253
psi_d = pi_h - np.arctan(crosstrack_y / self.lookahead_h) - beta_c
241254
psi_d = self.ssa(psi_d)
242255
return psi_d
243-
244-
def compute_theta_d(self, current_waypoint: State, next_waypoint: State, crosstrack_z: float, alpha_c: float) -> float:
256+
257+
def compute_theta_d(
258+
self,
259+
current_waypoint: State,
260+
next_waypoint: State,
261+
crosstrack_z: float,
262+
alpha_c: float,
263+
) -> float:
245264
pi_v = self.compute_pi_v(current_waypoint, next_waypoint)
246265
theta_d = pi_v + np.arctan(crosstrack_z / self.lookahead_v) + alpha_c
247266
theta_d = self.ssa(theta_d)
248267
return theta_d
249268

250269
@staticmethod
251270
def calculate_alpha_c(u: float, v: float, w: float, phi: float) -> float:
252-
return np.arctan((v * np.sin(phi) + w * np.cos(phi)) / u) # Slide 104 in Fossen 2024
253-
271+
return np.arctan(
272+
(v * np.sin(phi) + w * np.cos(phi)) / u
273+
) # Slide 104 in Fossen 2024
274+
254275
@staticmethod
255-
def calculate_beta_c(u: float, v: float, w: float, phi: float, theta: float, alpha_c: float) -> float:
256-
U_v = u * np.sqrt(1 + np.tan(alpha_c)**2)
257-
return np.arctan((v * np.cos(phi) - w * np.sin(phi)) / (U_v * np.cos(theta - alpha_c))) # Slide 104 in Fossen 2024
276+
def calculate_beta_c(
277+
u: float, v: float, w: float, phi: float, theta: float, alpha_c: float
278+
) -> float:
279+
U_v = u * np.sqrt(1 + np.tan(alpha_c) ** 2)
280+
return np.arctan(
281+
(v * np.cos(phi) - w * np.sin(phi)) / (U_v * np.cos(theta - alpha_c))
282+
) # Slide 104 in Fossen 2024

guidance/guidance_los/scripts/los_guidance_action_server.py

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ def _declare_topic_parameters(self):
136136

137137
def _initialize_publishers(self):
138138
"""Initialize all publishers."""
139-
140139
los_commands_topic = self.get_parameter('topics.publishers.los_commands').value
141140
self.get_logger().info(f"Publishing LOS commands to: {los_commands_topic}")
142141
self.guidance_cmd_pub = self.create_publisher(
@@ -221,24 +220,39 @@ def odom_callback(self, msg: Odometry):
221220
self.filter_initialized = True
222221

223222
def calculate_guidance(self) -> State:
224-
error = self.state.as_pos_array() - self.waypoints[self.current_waypoint_index].as_pos_array()
223+
error = (
224+
self.state.as_pos_array()
225+
- self.waypoints[self.current_waypoint_index].as_pos_array()
226+
)
225227
_, crosstrack_y, crosstrack_z = self.rotation_yz @ error
226-
alpha_c = self.guidance_calculator.calculate_alpha_c(self.u, self.v, self.w, self.phi)
227-
beta_c = self.guidance_calculator.calculate_beta_c(self.u, self.v, self.w, self.phi, self.pitch, alpha_c)
228-
psi_d = self.guidance_calculator.compute_psi_d(self.waypoints[self.current_waypoint_index],
229-
self.waypoints[self.current_waypoint_index + 1],
230-
crosstrack_y, beta_c)
231-
theta_d = self.guidance_calculator.compute_theta_d(self.waypoints[self.current_waypoint_index],
232-
self.waypoints[self.current_waypoint_index + 1],
233-
crosstrack_z, alpha_c)
234-
unfiltered_commands = State(surge_vel=self.desired_vel, pitch=theta_d, yaw=psi_d)
235-
filtered_commands = self.guidance_calculator.apply_reference_filter(unfiltered_commands)
228+
alpha_c = self.guidance_calculator.calculate_alpha_c(
229+
self.u, self.v, self.w, self.phi
230+
)
231+
beta_c = self.guidance_calculator.calculate_beta_c(
232+
self.u, self.v, self.w, self.phi, self.pitch, alpha_c
233+
)
234+
psi_d = self.guidance_calculator.compute_psi_d(
235+
self.waypoints[self.current_waypoint_index],
236+
self.waypoints[self.current_waypoint_index + 1],
237+
crosstrack_y,
238+
beta_c,
239+
)
240+
theta_d = self.guidance_calculator.compute_theta_d(
241+
self.waypoints[self.current_waypoint_index],
242+
self.waypoints[self.current_waypoint_index + 1],
243+
crosstrack_z,
244+
alpha_c,
245+
)
246+
unfiltered_commands = State(
247+
surge_vel=self.desired_vel, pitch=theta_d, yaw=psi_d
248+
)
249+
filtered_commands = self.guidance_calculator.apply_reference_filter(
250+
unfiltered_commands
251+
)
236252
return unfiltered_commands
237253

238-
239254
def execute_callback(self, goal_handle: ServerGoalHandle):
240255
"""Execute waypoint navigation action."""
241-
242256
# Initialize navigation goal with lock
243257
with self._goal_lock:
244258
self.goal_handle = goal_handle
@@ -253,8 +267,14 @@ def execute_callback(self, goal_handle: ServerGoalHandle):
253267
)
254268
self.waypoints.append(point_as_state)
255269

256-
self.pi_h = self.guidance_calculator.compute_pi_h(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
257-
self.pi_v = self.guidance_calculator.compute_pi_v(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
270+
self.pi_h = self.guidance_calculator.compute_pi_h(
271+
self.waypoints[self.current_waypoint_index],
272+
self.waypoints[self.current_waypoint_index + 1],
273+
)
274+
self.pi_v = self.guidance_calculator.compute_pi_v(
275+
self.waypoints[self.current_waypoint_index],
276+
self.waypoints[self.current_waypoint_index + 1],
277+
)
258278

259279
rotation_y = self.guidance_calculator.compute_rotation_y(self.pi_v)
260280
rotation_z = self.guidance_calculator.compute_rotation_z(self.pi_h)
@@ -280,22 +300,34 @@ def execute_callback(self, goal_handle: ServerGoalHandle):
280300
self.goal_handle = None
281301
result.success = False
282302
return result
283-
284-
norm = np.linalg.norm(self.state.as_pos_array() - self.waypoints[self.current_waypoint_index + 1].as_pos_array(), 2)
303+
304+
norm = np.linalg.norm(
305+
self.state.as_pos_array()
306+
- self.waypoints[self.current_waypoint_index + 1].as_pos_array(),
307+
2,
308+
)
285309
if norm < 0.5:
286310
self.get_logger().info('Waypoint reached')
287311
self.current_waypoint_index += 1
288312

289313
if self.current_waypoint_index >= len(self.waypoints) - 1:
290314
self.get_logger().info('All waypoints reached!')
291-
final_commands = State(surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw)
315+
final_commands = State(
316+
surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw
317+
)
292318
self.publish_guidance(final_commands)
293319
result.success = True
294320
self.goal_handle.succeed()
295321
return result
296-
297-
self.pi_h = self.guidance_calculator.compute_pi_h(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
298-
self.pi_v = self.guidance_calculator.compute_pi_v(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
322+
323+
self.pi_h = self.guidance_calculator.compute_pi_h(
324+
self.waypoints[self.current_waypoint_index],
325+
self.waypoints[self.current_waypoint_index + 1],
326+
)
327+
self.pi_v = self.guidance_calculator.compute_pi_v(
328+
self.waypoints[self.current_waypoint_index],
329+
self.waypoints[self.current_waypoint_index + 1],
330+
)
299331

300332
rotation_y = self.guidance_calculator.compute_rotation_y(self.pi_v)
301333
rotation_z = self.guidance_calculator.compute_rotation_z(self.pi_h)
@@ -314,6 +346,7 @@ def publish_guidance(self, commands: State):
314346
msg.yaw = commands.yaw
315347
self.guidance_cmd_pub.publish(msg)
316348

349+
317350
def main(args=None):
318351
"""Main function to initialize and run the guidance node."""
319352
rclpy.init(args=args)
@@ -332,4 +365,4 @@ def main(args=None):
332365

333366

334367
if __name__ == '__main__':
335-
main()
368+
main()

0 commit comments

Comments
 (0)