Skip to content

Commit d2ad008

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent 451c2c4 commit d2ad008

File tree

2 files changed

+118
-53
lines changed

2 files changed

+118
-53
lines changed

guidance/guidance_los/guidance_los/los_guidance_algorithm.py

Lines changed: 67 additions & 32 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

@@ -179,19 +179,27 @@ def compute_pitch_command(
179179
self.los_params.max_pitch_angle,
180180
)
181181

182-
def compute_desired_speed(self, yaw_error: float, distance_to_target: float, crosstrack_y: float) -> float:
183-
"""
184-
Compute speed command with yaw error, distance and crosstrack-based scaling.
185-
182+
def compute_desired_speed(
183+
self, yaw_error: float, distance_to_target: float, crosstrack_y: float
184+
) -> float:
185+
"""Compute speed command with yaw error, distance and crosstrack-based scaling.
186+
186187
Args:
187188
yaw_error: Error in yaw angle (radians)
188189
distance_to_target: Distance to target (meters)
189190
crosstrack_y: Cross-track error in y direction (meters)
190191
"""
191192
yaw_factor = np.cos(yaw_error)
192-
distance_factor = min(1.0, distance_to_target / self.los_params.lookahead_distance_max)
193+
distance_factor = min(
194+
1.0, distance_to_target / self.los_params.lookahead_distance_max
195+
)
193196
crosstrack_factor = 1.0 / (1.0 + abs(crosstrack_y))
194-
desired_speed = self.los_params.nominal_speed * yaw_factor * distance_factor * crosstrack_factor
197+
desired_speed = (
198+
self.los_params.nominal_speed
199+
* yaw_factor
200+
* distance_factor
201+
* crosstrack_factor
202+
)
195203
return max(self.los_params.min_speed, desired_speed)
196204

197205
def apply_reference_filter(self, commands: State) -> State:
@@ -217,50 +225,77 @@ def compute_pi_h(current_waypoint: State, next_waypoint: State) -> float:
217225
dx = next_waypoint.x - current_waypoint.x
218226
dy = next_waypoint.y - current_waypoint.y
219227
return np.arctan2(dy, dx)
220-
228+
221229
@staticmethod
222230
def compute_pi_v(current_waypoint: State, next_waypoint: State) -> float:
223231
dz = next_waypoint.z - current_waypoint.z
224-
horizontal_distance = np.sqrt((next_waypoint.x - current_waypoint.x)**2 + (next_waypoint.y - current_waypoint.y)**2)
232+
horizontal_distance = np.sqrt(
233+
(next_waypoint.x - current_waypoint.x) ** 2
234+
+ (next_waypoint.y - current_waypoint.y) ** 2
235+
)
225236
return np.arctan2(-dz, horizontal_distance)
226-
237+
227238
@staticmethod
228239
def compute_rotation_y(pi_v: float) -> np.ndarray:
229-
return np.array([
230-
[np.cos(pi_v), 0, np.sin(pi_v)],
231-
[0, 1, 0],
232-
[-np.sin(pi_v), 0, np.cos(pi_v)]
233-
])
234-
240+
return np.array(
241+
[
242+
[np.cos(pi_v), 0, np.sin(pi_v)],
243+
[0, 1, 0],
244+
[-np.sin(pi_v), 0, np.cos(pi_v)],
245+
]
246+
)
247+
235248
@staticmethod
236249
def compute_rotation_z(pi_h: float) -> np.ndarray:
237-
return np.array([
238-
[np.cos(pi_h), -np.sin(pi_h), 0],
239-
[np.sin(pi_h), np.cos(pi_h), 0],
240-
[0, 0, 1]
241-
])
242-
243-
def compute_psi_d(self, current_waypoint: State, next_waypoint: State, crosstrack_y: float, y_int: float) -> float:
250+
return np.array(
251+
[
252+
[np.cos(pi_h), -np.sin(pi_h), 0],
253+
[np.sin(pi_h), np.cos(pi_h), 0],
254+
[0, 0, 1],
255+
]
256+
)
257+
258+
def compute_psi_d(
259+
self,
260+
current_waypoint: State,
261+
next_waypoint: State,
262+
crosstrack_y: float,
263+
y_int: float,
264+
) -> float:
244265
pi_h = self.compute_pi_h(current_waypoint, next_waypoint)
245266
psi_d = pi_h - np.arctan((crosstrack_y + self.kappa) / self.lookahead_h)
246267
psi_d = self.ssa(psi_d)
247268
return psi_d
248-
249-
def compute_theta_d(self, current_waypoint: State, next_waypoint: State, crosstrack_z: float, alpha_c: float) -> float:
269+
270+
def compute_theta_d(
271+
self,
272+
current_waypoint: State,
273+
next_waypoint: State,
274+
crosstrack_z: float,
275+
alpha_c: float,
276+
) -> float:
250277
pi_v = self.compute_pi_v(current_waypoint, next_waypoint)
251278
theta_d = pi_v + np.arctan(crosstrack_z / self.lookahead_v) + alpha_c
252279
theta_d = self.ssa(theta_d)
253280
return theta_d
254281

255282
@staticmethod
256283
def calculate_alpha_c(u: float, v: float, w: float, phi: float) -> float:
257-
return np.arctan((v * np.sin(phi) + w * np.cos(phi)) / u) # Slide 104 in Fossen 2024
258-
284+
return np.arctan(
285+
(v * np.sin(phi) + w * np.cos(phi)) / u
286+
) # Slide 104 in Fossen 2024
287+
259288
@staticmethod
260-
def calculate_beta_c(u: float, v: float, w: float, phi: float, theta: float, alpha_c: float) -> float:
261-
U_v = u * np.sqrt(1 + np.tan(alpha_c)**2)
262-
return np.arctan((v * np.cos(phi) - w * np.sin(phi)) / (U_v * np.cos(theta - alpha_c))) # Slide 104 in Fossen 2024
289+
def calculate_beta_c(
290+
u: float, v: float, w: float, phi: float, theta: float, alpha_c: float
291+
) -> float:
292+
U_v = u * np.sqrt(1 + np.tan(alpha_c) ** 2)
293+
return np.arctan(
294+
(v * np.cos(phi) - w * np.sin(phi)) / (U_v * np.cos(theta - alpha_c))
295+
) # Slide 104 in Fossen 2024
263296

264297
def calculate_y_int_dot(self, crosstrack_y, y_int):
265-
y_int_dot = (self.lookahead_h * crosstrack_y) / (self.lookahead_h**2 + (crosstrack_y + self.kappa * y_int)**2)
266-
return y_int_dot
298+
y_int_dot = (self.lookahead_h * crosstrack_y) / (
299+
self.lookahead_h**2 + (crosstrack_y + self.kappa * y_int) ** 2
300+
)
301+
return y_int_dot

guidance/guidance_los/scripts/los_guidance_action_server.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ def __init__(self):
6464

6565
self.desired_vel = 0.3
6666
self.y_int = 0.0
67-
6867

6968
def declare_los_parameters_(self):
7069
"""Declare all LOS guidance parameters."""
@@ -133,7 +132,6 @@ def _declare_topic_parameters(self):
133132

134133
def _initialize_publishers(self):
135134
"""Initialize all publishers."""
136-
137135
los_commands_topic = self.get_parameter('topics.publishers.los_commands').value
138136
self.get_logger().info(f"Publishing LOS commands to: {los_commands_topic}")
139137
self.guidance_cmd_pub = self.create_publisher(
@@ -219,7 +217,10 @@ def odom_callback(self, msg: Odometry):
219217
self.filter_initialized = True
220218

221219
def calculate_guidance(self) -> State:
222-
error = self.state.as_pos_array() - self.waypoints[self.current_waypoint_index].as_pos_array()
220+
error = (
221+
self.state.as_pos_array()
222+
- self.waypoints[self.current_waypoint_index].as_pos_array()
223+
)
223224
_, crosstrack_y, crosstrack_z = self.rotation_yz @ error
224225
alpha_c = self.guidance_calculator.calculate_alpha_c(
225226
self.u, self.v, self.w, self.phi
@@ -233,8 +234,12 @@ def calculate_guidance(self) -> State:
233234
crosstrack_y,
234235
self.y_int,
235236
)
236-
y_int_dot = self.guidance_calculator.calculate_y_int_dot(crosstrack_y, self.y_int)
237-
self.y_int += y_int_dot * self.update_period # Forward euler, may need to use Runge-Kutta 4
237+
y_int_dot = self.guidance_calculator.calculate_y_int_dot(
238+
crosstrack_y, self.y_int
239+
)
240+
self.y_int += (
241+
y_int_dot * self.update_period
242+
) # Forward euler, may need to use Runge-Kutta 4
238243
theta_d = self.guidance_calculator.compute_theta_d(
239244
self.waypoints[self.current_waypoint_index],
240245
self.waypoints[self.current_waypoint_index + 1],
@@ -254,7 +259,6 @@ def calculate_guidance(self) -> State:
254259

255260
def execute_callback(self, goal_handle: ServerGoalHandle):
256261
"""Execute waypoint navigation action."""
257-
258262
# Initialize navigation goal with lock
259263
with self._goal_lock:
260264
self.goal_handle = goal_handle
@@ -269,15 +273,24 @@ def execute_callback(self, goal_handle: ServerGoalHandle):
269273
)
270274
self.waypoints.append(point_as_state)
271275

272-
self.pi_h = self.guidance_calculator.compute_pi_h(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
273-
self.pi_v = self.guidance_calculator.compute_pi_v(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
276+
self.pi_h = self.guidance_calculator.compute_pi_h(
277+
self.waypoints[self.current_waypoint_index],
278+
self.waypoints[self.current_waypoint_index + 1],
279+
)
280+
self.pi_v = self.guidance_calculator.compute_pi_v(
281+
self.waypoints[self.current_waypoint_index],
282+
self.waypoints[self.current_waypoint_index + 1],
283+
)
274284

275285
rotation_y = self.guidance_calculator.compute_rotation_y(self.pi_v)
276286
rotation_z = self.guidance_calculator.compute_rotation_z(self.pi_h)
277287
self.rotation_yz = rotation_y.T @ rotation_z.T
278288

279289
# Initialize y_int based on initial crosstrack error
280-
initial_error = self.state.as_pos_array() - self.waypoints[self.current_waypoint_index].as_pos_array()
290+
initial_error = (
291+
self.state.as_pos_array()
292+
- self.waypoints[self.current_waypoint_index].as_pos_array()
293+
)
281294
_, initial_crosstrack_y, _ = self.rotation_yz @ initial_error
282295
self.y_int = -initial_crosstrack_y / self.guidance_calculator.kappa
283296

@@ -286,7 +299,9 @@ def execute_callback(self, goal_handle: ServerGoalHandle):
286299

287300
rate = self.create_rate(1.0 / self.update_period)
288301
waiting_at_waypoint = False
289-
wait_cycles = int(1.0 / self.update_period) # Number of cycles for 1 second wait
302+
wait_cycles = int(
303+
1.0 / self.update_period
304+
) # Number of cycles for 1 second wait
290305
wait_count = 0
291306

292307
self.get_logger().info('Executing goal')
@@ -314,39 +329,53 @@ def execute_callback(self, goal_handle: ServerGoalHandle):
314329
if not waiting_at_waypoint:
315330
self.get_logger().info('Waypoint reached')
316331
waiting_at_waypoint = True
317-
stop_state = State(surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw)
332+
stop_state = State(
333+
surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw
334+
)
318335
self.guidance_calculator.reset_filter_state(stop_state)
319336

320337
if wait_count < wait_cycles:
321-
self.publish_guidance(State(surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw))
338+
self.publish_guidance(
339+
State(surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw)
340+
)
322341
wait_count += 1
323342
else:
324343
self.current_waypoint_index += 1
325344
# Reset integral term for new waypoint
326-
initial_error = self.state.as_pos_array() - self.waypoints[self.current_waypoint_index].as_pos_array()
345+
initial_error = (
346+
self.state.as_pos_array()
347+
- self.waypoints[self.current_waypoint_index].as_pos_array()
348+
)
327349
_, initial_crosstrack_y, _ = self.rotation_yz @ initial_error
328350
self.y_int = -initial_crosstrack_y / self.guidance_calculator.kappa
329351
waiting_at_waypoint = False
330352
wait_count = 0
331-
353+
332354
# Reset filter with stop state
333355
# self.guidance_calculator.reset_filter_state(stop_state)
334-
356+
335357
# # Publish stop command and wait briefly
336358
# self.publish_guidance(stop_state)
337359
# time.sleep(2.0) # Wait for 2 seconds to stabilize
338-
339360

340361
if self.current_waypoint_index >= len(self.waypoints) - 1:
341362
self.get_logger().info('All waypoints reached!')
342-
final_commands = State(surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw)
363+
final_commands = State(
364+
surge_vel=0.0, pitch=self.state.pitch, yaw=self.state.yaw
365+
)
343366
self.publish_guidance(final_commands)
344367
result.success = True
345368
self.goal_handle.succeed()
346369
return result
347-
348-
self.pi_h = self.guidance_calculator.compute_pi_h(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
349-
self.pi_v = self.guidance_calculator.compute_pi_v(self.waypoints[self.current_waypoint_index], self.waypoints[self.current_waypoint_index + 1])
370+
371+
self.pi_h = self.guidance_calculator.compute_pi_h(
372+
self.waypoints[self.current_waypoint_index],
373+
self.waypoints[self.current_waypoint_index + 1],
374+
)
375+
self.pi_v = self.guidance_calculator.compute_pi_v(
376+
self.waypoints[self.current_waypoint_index],
377+
self.waypoints[self.current_waypoint_index + 1],
378+
)
350379

351380
rotation_y = self.guidance_calculator.compute_rotation_y(self.pi_v)
352381
rotation_z = self.guidance_calculator.compute_rotation_z(self.pi_h)
@@ -366,6 +395,7 @@ def publish_guidance(self, commands: State):
366395
msg.yaw = commands.yaw
367396
self.guidance_cmd_pub.publish(msg)
368397

398+
369399
def main(args=None):
370400
"""Main function to initialize and run the guidance node."""
371401
rclpy.init(args=args)
@@ -384,4 +414,4 @@ def main(args=None):
384414

385415

386416
if __name__ == '__main__':
387-
main()
417+
main()

0 commit comments

Comments
 (0)