Skip to content

Commit 3295c77

Browse files
authored
Added support for local and relative frames (#109)
* Added support for local and relative frames * Removed unused variables * Remove broken attempt at using frames in collision avoidance * Remove stale print statement * Fixed unit tests * Cleaned up clock synchronization error * Updated stale docstring * Update stale docstrings * Resolve stale argument names
1 parent 2a935d6 commit 3295c77

File tree

11 files changed

+596
-475
lines changed

11 files changed

+596
-475
lines changed

examples/collision_prevention.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ def main() -> None:
150150
# Command the agent to the target location
151151
# We don't need to specify the target agents because these are captured from the
152152
# config file
153-
future = mavswarm.goto(config_file=args.config, retry=True)
153+
future = mavswarm.goto(
154+
config_file=args.config, retry=True, frame=MavSwarm.GLOBAL_RELATIVE_FRAME
155+
)
154156
future.add_done_callback(print_message_response_cb)
155157

156158
while not future.done():

examples/goto.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ def main() -> None:
146146
# Command the agent to the target location
147147
# We don't need to specify the target agents because these are captured from the
148148
# config file
149-
future = mavswarm.goto(config_file=args.config, retry=True)
149+
future = mavswarm.goto(
150+
config_file=args.config, retry=True, frame=MavSwarm.GLOBAL_RELATIVE_FRAME
151+
)
150152
future.add_done_callback(print_message_response_cb)
151153

152154
while not future.done():

pymavswarm/agent.py

Lines changed: 73 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@
1616

1717
from __future__ import annotations
1818

19-
from collections import deque
2019
from datetime import datetime
21-
from statistics import fmean
2220
from typing import Any
2321

2422
import monotonic
23+
from pymavlink import mavutil
2524

2625
import pymavswarm.state as swarm_state
2726
from pymavswarm.mission import SwarmMission
@@ -81,23 +80,16 @@ def __init__(
8180
self.__gps_info = swarm_state.GPSInfo(
8281
0.0, 0.0, 0, 0, optional_context_props=context_props
8382
)
84-
self.__position = swarm_state.Position(
85-
0.0, 0.0, 0.0, optional_context_props=context_props
86-
)
83+
self.__position = swarm_state.Position(optional_context_props=context_props)
8784
self.__ekf = swarm_state.EKFStatus(
8885
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, optional_context_props=context_props
8986
)
9087
self.__telemetry = swarm_state.Telemetry(
9188
0.0, optional_context_props=context_props
9289
)
93-
self.__velocity = swarm_state.Velocity(
94-
0.0, 0.0, 0.0, optional_context_props=context_props
95-
)
96-
self.__prev_velocity = swarm_state.Velocity(
97-
0.0, 0.0, 0.0, optional_context_props=context_props
98-
)
90+
self.__velocity = swarm_state.Velocity(optional_context_props=context_props)
9991
self.__acceleration = swarm_state.Acceleration(
100-
0.0, 0.0, 0.0, optional_context_props=context_props
92+
optional_context_props=context_props
10193
)
10294
self.__armed = swarm_state.Generic(
10395
"armed", False, optional_context_props=context_props
@@ -129,23 +121,23 @@ def __init__(
129121
self.__last_params_read = swarm_state.ParameterList(
130122
max_length=max_params_stored, optional_context_props=context_props
131123
)
132-
self.__home_position = swarm_state.Position(
133-
0.0, 0.0, 0.0, optional_context_props=context_props
124+
self.__home_position = swarm_state.Vector(
125+
0.0,
126+
0.0,
127+
0.0,
128+
mavutil.mavlink.MAV_FRAME_GLOBAL,
129+
0.0,
130+
optional_context_props=context_props,
134131
)
135132
self.__hrl_state = swarm_state.Generic(
136133
"hrl_state", None, optional_context_props=context_props
137134
)
138135
self.__ping = swarm_state.Generic(
139136
"ping", 0, optional_context_props=context_props
140137
)
141-
self.__last_position_message_timestamp = swarm_state.Generic(
142-
"time_boot_ms", 0, optional_context_props=context_props
138+
self.__clock_offset = swarm_state.Generic(
139+
"clock_offset", 0, optional_context_props=context_props
143140
)
144-
self.__clock_offset: deque[int] = deque(maxlen=5)
145-
146-
# Initialize the clock offset with a value so that we have something to access
147-
# at boot
148-
self.__clock_offset.append(0)
149141

150142
return
151143

@@ -249,16 +241,6 @@ def velocity(self) -> swarm_state.Velocity:
249241
"""
250242
return self.__velocity
251243

252-
@property
253-
def previous_velocity(self) -> swarm_state.Velocity:
254-
"""
255-
Velocity of the agent at the previous measurement.
256-
257-
return: agent's previous velocity
258-
:rtype: Velocity
259-
"""
260-
return self.__prev_velocity
261-
262244
@property
263245
def acceleration(self) -> swarm_state.Acceleration:
264246
"""
@@ -370,7 +352,7 @@ def last_params_read(self) -> swarm_state.ParameterList:
370352
return self.__last_params_read
371353

372354
@property
373-
def home_position(self) -> swarm_state.Position:
355+
def home_position(self) -> swarm_state.Vector:
374356
"""
375357
Home position of the agent.
376358
@@ -400,36 +382,14 @@ def ping(self) -> Generic:
400382
return self.__ping
401383

402384
@property
403-
def last_position_message_timestamp(self) -> Generic:
404-
"""
405-
Most recent time that the agent sent the position message in the global clock.
406-
407-
:return: time since boot [ms]
408-
:rtype: Generic
385+
def clock_offset(self) -> Generic:
409386
"""
410-
return self.__last_position_message_timestamp
411-
412-
def update_clock_offset(self, offset: int) -> None:
413-
"""
414-
Update the agent clock offset relative to the source clock.
415-
416-
:param offset: clock offset
417-
:type offset: int
418-
"""
419-
self.__clock_offset.append(offset)
420-
return
421-
422-
@property
423-
def clock_offset(self) -> int:
424-
"""
425-
Average offset between the source clock and agent clock.
426-
427-
This property does not implement a watcher interface.
387+
Clock offset from the global clock.
428388
429389
:return: clock offset
430-
:rtype: int
390+
:rtype: Generic
431391
"""
432-
return int(fmean(self.__clock_offset))
392+
return self.__clock_offset
433393

434394
def compute_reachable_set(
435395
self,
@@ -438,11 +398,14 @@ def compute_reachable_set(
438398
reach_time: float,
439399
initial_step_size: float = 0.5,
440400
reach_timeout: float = 0.001,
441-
uses_lat_lon: bool = True,
442401
) -> tuple[HyperRectangle, float]:
443402
"""
444403
Compute the current reachable set of the agent.
445404
405+
In the current state of the system, the reachable set is only computed using
406+
the global frame. In the future, if transformations between frames is
407+
implemented, reachable set computation for multiple frames may be supported.
408+
446409
:param position_error: 3D position error to account for in the measurement
447410
:type position_error: float
448411
:param velocity_error: 3D velocity error to account for in the measurement
@@ -460,103 +423,71 @@ def compute_reachable_set(
460423
from the start time
461424
:rtype: tuple[HyperRectangle, float]
462425
"""
463-
if uses_lat_lon:
464-
rect = HyperRectangle(
465-
[
466-
# When computing the reachable state for the GPS position, we pass
467-
# the origin as the position. After computing what the reachable
468-
# change, we correct the original lat/lon position. This
469-
# helps us eliminate *some* error that occurs during
470-
# conversions.
471-
Interval(-position_error, position_error),
472-
Interval(-position_error, position_error),
473-
Interval(
474-
self.position.z - position_error,
475-
self.position.z + position_error,
476-
),
477-
Interval(
478-
self.velocity.x - velocity_error,
479-
self.velocity.x + velocity_error,
480-
),
481-
Interval(
482-
self.velocity.y - velocity_error,
483-
self.velocity.y + velocity_error,
484-
),
485-
Interval(
486-
self.velocity.z - velocity_error,
487-
self.velocity.z + velocity_error,
488-
),
489-
]
490-
)
491-
else:
492-
rect = HyperRectangle(
493-
[
494-
Interval(
495-
self.position.x - position_error,
496-
self.position.x + position_error,
497-
),
498-
Interval(
499-
self.position.y - position_error,
500-
self.position.y + position_error,
501-
),
502-
Interval(
503-
self.position.z - position_error,
504-
self.position.z + position_error,
505-
),
506-
Interval(
507-
self.velocity.x - velocity_error,
508-
self.velocity.x + velocity_error,
509-
),
510-
Interval(
511-
self.velocity.y - velocity_error,
512-
self.velocity.y + velocity_error,
513-
),
514-
Interval(
515-
self.velocity.z - velocity_error,
516-
self.velocity.z + velocity_error,
517-
),
518-
]
519-
)
426+
# When computing the reachable state for the GPS position, we pass
427+
# the origin as the position. After computing what the reachable
428+
# change, we correct the original lat/lon position. This
429+
# helps us eliminate *some* error that occurs during
430+
# conversions.
431+
rect = HyperRectangle(
432+
[
433+
Interval(-position_error, position_error),
434+
Interval(-position_error, position_error),
435+
Interval(
436+
self.position.global_frame.z - position_error,
437+
self.position.global_frame.z + position_error,
438+
),
439+
Interval(
440+
self.velocity.global_frame.x - velocity_error,
441+
self.velocity.global_frame.x + velocity_error,
442+
),
443+
Interval(
444+
self.velocity.global_frame.y - velocity_error,
445+
self.velocity.global_frame.y + velocity_error,
446+
),
447+
Interval(
448+
self.velocity.global_frame.z - velocity_error,
449+
self.velocity.global_frame.z + velocity_error,
450+
),
451+
]
452+
)
520453

521454
# Compute the sender's reachable state
522455
(
523456
reachable_state,
524457
reach_time_elapsed,
525458
) = SafetyChecker.face_lifting_iterative_improvement(
526459
rect,
527-
self.last_position_message_timestamp.value,
460+
self.position.global_frame.timestamp,
528461
(
529-
self.acceleration.x,
530-
self.acceleration.y,
531-
self.acceleration.z,
462+
self.acceleration.global_frame.x,
463+
self.acceleration.global_frame.y,
464+
self.acceleration.global_frame.z,
532465
),
533466
reach_time,
534467
initial_step_size=initial_step_size,
535468
timeout=reach_timeout,
536469
)
537470

538-
if uses_lat_lon:
539-
# Correct the latitude using the reachable positions
540-
reachable_state.intervals[0].interval_min = latitude_conversion(
541-
self.position.x,
542-
reachable_state.intervals[0].interval_min,
543-
)
544-
reachable_state.intervals[0].interval_max = latitude_conversion(
545-
self.position.x,
546-
reachable_state.intervals[0].interval_max,
547-
)
548-
549-
# Correct the longitude using the reachable positions
550-
reachable_state.intervals[1].interval_min = longitude_conversion(
551-
self.position.x,
552-
self.position.y,
553-
reachable_state.intervals[1].interval_min,
554-
)
555-
reachable_state.intervals[1].interval_max = longitude_conversion(
556-
self.position.x,
557-
self.position.y,
558-
reachable_state.intervals[1].interval_max,
559-
)
471+
# Correct the positions to use latitude and longitude
472+
reachable_state.intervals[0].interval_min = latitude_conversion(
473+
self.position.global_frame.x,
474+
reachable_state.intervals[0].interval_min,
475+
)
476+
reachable_state.intervals[0].interval_max = latitude_conversion(
477+
self.position.global_frame.x,
478+
reachable_state.intervals[0].interval_max,
479+
)
480+
481+
reachable_state.intervals[1].interval_min = longitude_conversion(
482+
self.position.global_frame.x,
483+
self.position.global_frame.y,
484+
reachable_state.intervals[1].interval_min,
485+
)
486+
reachable_state.intervals[1].interval_max = longitude_conversion(
487+
self.position.global_frame.x,
488+
self.position.global_frame.y,
489+
reachable_state.intervals[1].interval_max,
490+
)
560491

561492
return reachable_state, reach_time_elapsed
562493

0 commit comments

Comments
 (0)