|
1 | 1 | import time |
2 | | -from math import atan |
| 2 | +from math import atan, pi |
3 | 3 | from dataclasses import dataclass |
4 | 4 | from typing import Optional, Iterable |
5 | 5 |
|
@@ -167,6 +167,14 @@ def get_local_position(self) -> LocationLocal: |
167 | 167 | return LocationLocal(msg.x, msg.y, msg.z) |
168 | 168 | return LocationLocal(0, 0, 0) |
169 | 169 |
|
| 170 | + def get_local_position_ned(self) -> tuple[float, float, float]: |
| 171 | + """ |
| 172 | + Convenience helper returning the current local NED position as a tuple |
| 173 | + (north, east, down), mirroring older Navigator APIs. |
| 174 | + """ |
| 175 | + pos = self.get_local_position() |
| 176 | + return (pos.north, pos.east, pos.down) |
| 177 | + |
170 | 178 | def get_velocity(self) -> Velocity: |
171 | 179 | """Returns the current local velocity in NED.""" |
172 | 180 | msg = self.master.recv_match(type="LOCAL_POSITION_NED", blocking=True) |
@@ -211,6 +219,23 @@ def set_position_local_ned(self, setpoint: PositionSetpointLocal, |
211 | 219 | ) |
212 | 220 | LOGGER.debug("Local setpoint sent: %s", setpoint) |
213 | 221 |
|
| 222 | + def set_position_target_local_ned( |
| 223 | + self, |
| 224 | + x: float = 0, |
| 225 | + y: float = 0, |
| 226 | + z: float = 0, |
| 227 | + coordinate_frame: int = mavutil.mavlink.MAV_FRAME_LOCAL_NED, |
| 228 | + type_mask: int = 0x07FF, |
| 229 | + ) -> None: |
| 230 | + """ |
| 231 | + Convenience wrapper mirroring older set_position_target_local_ned APIs. |
| 232 | +
|
| 233 | + Uses position-only setpoints; velocity/acceleration/yaw fields are left |
| 234 | + at their defaults. |
| 235 | + """ |
| 236 | + setpoint = PositionSetpointLocal(x=x, y=y, z=z) |
| 237 | + self.set_position_local_ned(setpoint, coordinate_frame=coordinate_frame, type_mask=type_mask) |
| 238 | + |
214 | 239 | def set_position_global(self, setpoint: PositionSetpointGlobal, |
215 | 240 | coordinate_frame: int = mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT, |
216 | 241 | type_mask: int = 0x07FF) -> None: |
@@ -372,6 +397,82 @@ def set_speed(self, speed: float) -> None: |
372 | 397 | ) |
373 | 398 | LOGGER.info("Global speed set to %.2f m/s", speed) |
374 | 399 |
|
| 400 | + # ---------------------- |
| 401 | + # Heading / relative helpers |
| 402 | + # ---------------------- |
| 403 | + def set_heading(self, heading: float, yaw_rate: float = 0) -> None: |
| 404 | + """ |
| 405 | + Set absolute heading using MAV_CMD_CONDITION_YAW. |
| 406 | +
|
| 407 | + Args: |
| 408 | + heading: Target yaw in degrees. |
| 409 | + yaw_rate: Desired yaw rate in deg/s (0 to use default). |
| 410 | + """ |
| 411 | + self.master.mav.command_long_send( |
| 412 | + self.master.target_system, |
| 413 | + self.master.target_component, |
| 414 | + mavutil.mavlink.MAV_CMD_CONDITION_YAW, |
| 415 | + 0, |
| 416 | + heading, |
| 417 | + yaw_rate, |
| 418 | + 1, # direction: 1=cw, -1=ccw |
| 419 | + 0, # absolute angle |
| 420 | + 0, |
| 421 | + 0, |
| 422 | + 0, |
| 423 | + ) |
| 424 | + LOGGER.info("Set heading to %.2f degrees", heading) |
| 425 | + |
| 426 | + def set_heading_relative(self, delta_heading: float, yaw_rate: float = 0) -> None: |
| 427 | + """ |
| 428 | + Adjust heading relative to current using MAV_CMD_CONDITION_YAW. |
| 429 | +
|
| 430 | + Args: |
| 431 | + delta_heading: Relative yaw change in degrees (positive cw). |
| 432 | + yaw_rate: Desired yaw rate in deg/s (0 to use default). |
| 433 | + """ |
| 434 | + self.master.mav.command_long_send( |
| 435 | + self.master.target_system, |
| 436 | + self.master.target_component, |
| 437 | + mavutil.mavlink.MAV_CMD_CONDITION_YAW, |
| 438 | + 0, |
| 439 | + delta_heading, |
| 440 | + yaw_rate, |
| 441 | + 1, |
| 442 | + 1, # relative angle |
| 443 | + 0, |
| 444 | + 0, |
| 445 | + 0, |
| 446 | + ) |
| 447 | + LOGGER.info("Adjusted heading by %.2f degrees", delta_heading) |
| 448 | + |
| 449 | + def set_position_relative(self, d_north: float, d_east: float, d_alt: float = 0.0) -> None: |
| 450 | + """ |
| 451 | + Move the vehicle by a relative offset in metres using global coordinates. |
| 452 | +
|
| 453 | + Args: |
| 454 | + d_north: Distance to move north in metres. |
| 455 | + d_east: Distance to move east in metres. |
| 456 | + d_alt: Change in altitude in metres (positive up). |
| 457 | + """ |
| 458 | + current = self.get_global_position() |
| 459 | + if current.alt is None: |
| 460 | + current_alt = 0.0 |
| 461 | + else: |
| 462 | + current_alt = current.alt |
| 463 | + |
| 464 | + # Spherical Earth approximation (same as older Navigator) |
| 465 | + earth_radius = 6378137.0 |
| 466 | + d_lat = d_north / earth_radius |
| 467 | + d_lon = d_east / (earth_radius * cos(pi * current.lat / 180.0)) |
| 468 | + |
| 469 | + new_lat = current.lat + (d_lat * 180.0 / pi) |
| 470 | + new_lon = current.lon + (d_lon * 180.0 / pi) |
| 471 | + new_alt = current_alt + d_alt |
| 472 | + |
| 473 | + target = PositionSetpointGlobal(lat=new_lat, lon=new_lon, alt=new_alt) |
| 474 | + self.set_position_global(target) |
| 475 | + |
375 | 476 | def set_servo(self, servo_number, pwm_value): |
376 | 477 | """ |
377 | 478 | servo_number: Output channel (1-16) |
|
0 commit comments