Skip to content

AP_Mission: fix get_next_nav_cmd - #33863

Open
robertlong13 wants to merge 8 commits into
ArduPilot:masterfrom
robertlong13:pr/fix-get-next-nav-cmd
Open

AP_Mission: fix get_next_nav_cmd#33863
robertlong13 wants to merge 8 commits into
ArduPilot:masterfrom
robertlong13:pr/fix-get-next-nav-cmd

Conversation

@robertlong13

@robertlong13 robertlong13 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Classification & Testing

  • Checked by a human programmer
  • Non-functional change
  • No-binary change
  • Infrastructure change (e.g. unit tests, helper scripts)
  • Automated test(s) verify changes (e.g. unit test, autotest)
  • Tested manually, description below (e.g. SITL)
  • Tested on hardware
  • Logs attached
  • Logs available on request

Description

This supersedes PRs #32029 and #31038

On Copter, the next nav command is fetched before it is executed: get_next_nav_cmd supplies the target the S-curve calculations are shaped through, and advance_current_nav_cmd later loads what actually runs. Nothing guaranteed the two resolved the mission the same way, and around jumps they did not (as reported in #21534), causing Copter to claim it was flying to one waypoint (after it advances it) while flying to a different one (the one it fetched before). On Plane, we don't pre-fetch the next waypoint, so this bug has less dramatic impact.

They disagreed because get_next_nav_cmd never actually followed a jump. The scan handed get_next_cmd a linearly incremented index each pass, so a jump's target was only glanced at (accepted if it happened to be a nav command directly, discarded otherwise) until the index walked past the jump and the scan continued in the linear sequence beyond it, reporting a nav command the vehicle may not fly next (and in fact, may never fly if that was an infinite jump).

A jump tag is never a nav command, so DO_JUMP_TAG missions tripped this every time, but tags are just the common case of jumping to a non-nav command; a DO_JUMP which targets any non-nav command fails the same way.

The fix builds on a small mechanism: the jump-resolving look-ups (get_next_cmd, get_jump_times_run, increment_jump_times_run) gain an optional jump cursor, a private copy of the do-jump counters, so a caller can scan ahead following jumps exactly as execution would, without spending the running mission's counters. Fetch and execute now walk the same resolution path, differing only in whose counters they spend, so they cannot disagree. That cursor replaces the two workarounds that existed for the same problem: the backup/restore-through-goto dance in the distance scans, and the send_gcs_msg flag (a jump is now reported to the GCS exactly when the live counters are the ones being advanced). get_next_cmd also takes over advancing the caller's scan position, so the resume-from-the-resolved-index rule is written once instead of six times: previously two of those six were wrong, including distance_to_landing's inner scan (couldn't find anyone reporting any bugs from that, but that could have in theory messed with the logic for finding the closest landing on Plane).

While bounding these searches, set_current_cmd turned out to have an unbounded one: a jump loop containing no nav command (a forever DO_JUMP over do commands) never terminates the search and hangs the main loop. It's now bounded the same way advance_current_nav_cmd bounds its own, failing the command instead. New autotests cover the four jump shapes (DO_JUMP and DO_JUMP_TAG, each landing on a nav and on a do command) by checking the vehicle's actual position rather than the reported waypoint, plus the set-current hang on Rover.

@rmackay9

Copy link
Copy Markdown
Contributor

This is really great to see and, "a jump loop containing no nav command (a forever DO_JUMP over do commands) never terminates the search and hangs the main loop" sounds like a serious bug we need to fix. Thanks!

Fly a mission that loops forever over one leg, with unreachable
waypoints placed 300m beyond the jump, and check the vehicle keeps to
the leg. Position is checked rather than the reported current waypoint,
which advances correctly even when the lookahead used to shape the path
does not.

Four shapes are covered: a DO_JUMP to a do command, a DO_JUMP straight
to a nav command, a DO_JUMP_TAG whose tag is followed by a nav command,
and a DO_JUMP_TAG whose tag is followed by a do command.
get_next_cmd, get_jump_times_run and increment_jump_times_run resolve
do-jumps against the live _jump_tracking counters, so any caller
scanning the mission ahead of the vehicle either disturbs the counters
the running mission depends on, or has to save and restore them around
the scan.

Give the three of them an optional jump cursor to use in place of
_jump_tracking. A caller that supplies one gets jumps resolved and
counted exactly as execution would, against its own copy. Passing
nullptr keeps the live counters, so nothing changes for existing
callers; no caller supplies a cursor yet.
distance_to_landing and distance_to_mission_leg copied _jump_tracking
into a local array, let the scan mutate the live counters, then copied
them back on the way out. Every exit had to route through a goto so the
restore could not be missed.

Seed a jump cursor from the live counters and scan on that instead. The
counters are never touched, so the backup, the restore and the two goto
labels go away and each exit can simply return. No change in behavior.
send_gcs_msg was the other half of the workaround the jump cursor has
just replaced: a scan running against the live counters had to ask for
silence, or it would announce jumps the vehicle was not taking.

Every caller that wanted silence wanted it because it was scanning
ahead, which the cursor now states outright, so the flag carries no
information the cursor does not. Drop it and report a jump exactly when
the live counters are the ones being advanced. A look-ahead can no
longer announce a jump by forgetting an argument. No change in
behavior.
get_next_nav_cmd stepped its own index after each command, so a jump
followed during the scan was discarded: the search resumed in the linear
sequence past the jump rather than at the command the jump resolved to,
and reported a nav command the vehicle will never fly. On Copter that
answer becomes the next destination handed to the S-curve, so the
vehicle flies toward it. A DO_JUMP landing directly on a nav command hid
the fault, as the scan returned on its first pass; DO_JUMP_TAG always
tripped it, the tag marker never being a nav command.

Resume from the index the scan resolved to, which is what
advance_current_nav_cmd already does, and walk a private jump cursor so
the look-ahead follows finite jumps as execution would without spending
the running mission's counters.

Fixes issues 31021 and 21534.
Six scans walk the mission through get_next_cmd, and each one decided
for itself where to look next. Three resumed from the index the scan
resolved to; get_next_nav_cmd stepped its own index, which was the
previous commit's bug, and distance_to_landing's inner scan still does,
disagreeing with the outer loop of the very same function. The rule is
easy to state and was written out six times, wrongly twice.

Hand the rule to get_next_cmd: scan_index becomes the caller's position
in the mission and is left pointing at the command to resume from, so a
scan continued through this function follows jumps the way the running
mission does. Every caller drops its own copy, and is_takeoff_next stops
re-finding the nav command it just examined.

This is a consistency change rather than a fix -- the previous commit
already closed the reported bug -- but it also corrects
distance_to_landing, which nothing in the test suite covers, and it
means the seventh scan cannot reintroduce this.
Setting the current command searches forward for a nav command to load,
following jumps on the way. A forever DO_JUMP over do commands never
runs out of repeats and never reaches a nav command, so the search has
nothing to terminate on.

Upload such a mission and set the current command. Without a bound on
the search the vehicle stops answering: the run_cmd below gets no ack
and sim time stops advancing, because the main loop never comes back.
set_current_cmd walks the mission looking for a nav command to load, and
the walk follows jumps. Nothing bounded it, so a jump loop holding no
nav command spun forever inside the main loop: a forever DO_JUMP never
exhausts its repeats, and no nav command is ever reached to end the
search. get_next_cmd's own guard does not help, as it counts the jumps
followed within a single call and this walk follows one per call.

Bound the search the same way advance_current_nav_cmd bounds its own,
and report failure when the bound is hit. Checking the counter inside
the loop rather than after it keeps the test honest, as a post-decrement
guard wraps back to 255 on the iteration that ends the loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants