Skip to content

Guidance

Guidance is the outer loop of the control cascade: each tick it turns a mission into the setpoint the controller tracks. It's a component of the loop, for a controller that takes setpoints, and its stage runs after the estimator's and before the controller's, so a setpoint applies on the tick that computes it. It reads the vehicle's position from the estimate. A guidance holds no controller and no stop. Its stage writes a changed setpoint to the setpoint signal, which the controller reads, and the loop checks before any stage runs that the controller reads the type the guidance writes. When the mission is over, the stage marks the tick done, and the loop ends the run. PX4 takes none, because its own navigator sequences its missions.

A flight constructs its guidance from the parameters of the guidance, hands it to the loop, and keeps the object. It sets the mission on it before the run and reads its telemetry after. Sim gives no role an accessor of its own. The classes are internal: an example imports them from nexus_uav._src.guidance, not from the top-level package.

guidance = MissionGuidance(reached_m=0.3, final_hold_s=2.0)
guidance.set_mission([(3.0, 0.0, 2.0), (3.0, 3.0, 2.5)])
with nx.Sim._from_loop(orch, guidance=guidance) as sim:
    sim.run()

The guidance logs its waypoint markers at sim/guidance/waypoints/wp_<i> and a tracked reference at sim/guidance/reference.

MissionGuidance

MissionGuidance(
    *, reached_m: float = 0.3, final_hold_s: float = 2.0
)

Bases: Guidance

Sequence a mission of position goals: advance on arrival, and end the run after the final hold.

The stage runs each tick before the controller's: it detects arrival at the active goal, writes the next goal to the setpoint, a PositionGoal the controller reads, and marks the tick done once the final goal has held for final_hold_s, which ends the run. The start runs the stage too, so the controller holds the first goal before its own first stage; a mission of two or more goals advances between graph replays.

A subclass, such as the policy example's geofence, overrides _tick, the stage's work over the vehicle's position, which the stage reads from the estimate, and the sim time. It can command a setpoint with _command, read and set _done, which freezes the sequencing and ends the run, and log through _logger. The rest is this class's own.

PARAMETER DESCRIPTION
reached_m

Advance to the next goal within this distance [m] of the active one.

TYPE: float DEFAULT: 0.3

final_hold_s

Keep running this long, in sim time, after the vehicle reaches the final goal.

TYPE: float DEFAULT: 2.0

Source code in nexus_uav/_src/guidance/mission.py
33
34
35
36
37
38
39
40
def __init__(self, *, reached_m: float = 0.3, final_hold_s: float = 2.0):
    super().__init__(setpoint=Signal("setpoint", wp.vec3, shape=(1,)))
    self._reached_m = float(reached_m)
    self._final_hold_s = float(final_hold_s)
    self._end_at: float | None = None  # sim time at which the mission is over, set on the final goal
    self._announced = False  # log the first gold marker on the stage's first run, with a valid timeline
    # Mission telemetry, which post-run evaluation reads: the sim time the vehicle reached each goal.
    self.arrival_times: list[float] = []

arrival_times instance-attribute

arrival_times: list[float] = []

reached property

reached: int

How many mission goals the vehicle has reached, for a post-run assertion.

active_setpoint property

active_setpoint: PositionGoal | None

The active commanded goal, or None until set_mission runs or once the vehicle has reached every goal.

goto

goto(setpoint: Setpoint) -> None

Issue one setpoint, a single-goal mission.

PARAMETER DESCRIPTION
setpoint

The target: a :class:PositionGoal or a bare (x, y, z) position.

TYPE: Setpoint

Source code in nexus_uav/_src/guidance/mission.py
42
43
44
45
46
47
48
def goto(self, setpoint: Setpoint) -> None:
    """Issue one setpoint, a single-goal mission.

    Args:
        setpoint: The target: a :class:`PositionGoal` or a bare ``(x, y, z)`` position.
    """
    self.set_mission([setpoint])

set_mission

set_mission(setpoints: list[Setpoint]) -> None

Set the mission; the guidance advances through it on arrival and owns the run's end.

Call before sim.run(): the stage writes the first goal to the tick on its next run, the warm pass of a run that hasn't started, and the guidance advances on arrival.

PARAMETER DESCRIPTION
setpoints

Ordered mission setpoints, each a :class:PositionGoal or a bare (x, y, z) position. Must be non-empty.

TYPE: list[Setpoint]

RAISES DESCRIPTION
ValueError

setpoints is empty.

Source code in nexus_uav/_src/guidance/mission.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def set_mission(self, setpoints: list[Setpoint]) -> None:
    """Set the mission; the guidance advances through it on arrival and owns the run's end.

    Call before ``sim.run()``: the stage writes the first goal to the tick on its next run, the warm
    pass of a run that hasn't started, and the guidance advances on arrival.

    Args:
        setpoints: Ordered mission setpoints, each a :class:`PositionGoal` or a bare ``(x, y, z)``
            position. Must be non-empty.

    Raises:
        ValueError: ``setpoints`` is empty.
    """
    mission = [self._as_position_goal(s) for s in setpoints]
    if not mission:
        raise ValueError("set_mission needs at least one setpoint")
    self._mission = mission
    self._active = 0
    self._end_at = None
    self._done = False
    self._announced = False
    self.arrival_times = []
    self._command(mission[0])

TrackingGuidance

TrackingGuidance(
    *, planner: Callable, final_hold_s: float = 2.0
)

Bases: Guidance

Plan the whole-path reference once, in the run's start, and write it to the setpoint.

The planner takes the mission's waypoints and returns a reference that exposes set_start, duration and reference_path. The guidance plans in the run's start, which gives it the start position, and writes a ReferenceTrajectory to the setpoint, a host signal the controller reads at the start's exchange. It marks the tick done once the trajectory's duration plus final_hold_s has passed, which ends the run. reference_started_at is the sim time of that write, the reference clock's anchor, which post-run evaluation reads.

PARAMETER DESCRIPTION
planner

waypoints -> reference, the flight file's own, acados' min-snap planner.

TYPE: Callable

final_hold_s

Keep running this long, in sim time, after the trajectory completes.

TYPE: float DEFAULT: 2.0

Source code in nexus_uav/_src/guidance/tracking.py
32
33
34
35
36
37
38
def __init__(self, *, planner: Callable, final_hold_s: float = 2.0):
    super().__init__(setpoint=Signal("setpoint", ReferenceTrajectory))
    self._planner = planner
    self._final_hold_s = float(final_hold_s)
    self._end_at: float | None = None
    self._planned = False
    self.reference_started_at: float | None = None

reference_started_at instance-attribute

reference_started_at: float | None = None

set_mission

set_mission(setpoints: list[Setpoint]) -> None

Set the waypoints the reference flies through; the guidance plans them in the run's start.

PARAMETER DESCRIPTION
setpoints

Ordered waypoints, each a :class:PositionGoal or a bare (x, y, z) position. Must be non-empty.

TYPE: list[Setpoint]

RAISES DESCRIPTION
ValueError

setpoints is empty.

Source code in nexus_uav/_src/guidance/tracking.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def set_mission(self, setpoints: list[Setpoint]) -> None:
    """Set the waypoints the reference flies through; the guidance plans them in the run's start.

    Args:
        setpoints: Ordered waypoints, each a :class:`PositionGoal` or a bare ``(x, y, z)`` position.
            Must be non-empty.

    Raises:
        ValueError: ``setpoints`` is empty.
    """
    mission = [self._as_position_goal(s) for s in setpoints]
    if not mission:
        raise ValueError("set_mission needs at least one setpoint")
    self._mission = mission
    self._active = 0
    self._end_at = None
    self._done = False
    self._planned = False
    self.reference_started_at = None