Skip to content

Copter: do not treat a stale baro sample as evidence of not falling - #34033

Open
peterbarker wants to merge 2 commits into
ArduPilot:masterfrom
peterbarker:pr-claude2/parachute-stale-baro
Open

peterbarker wants to merge 2 commits into
ArduPilot:masterfrom
peterbarker:pr-claude2/parachute-stale-baro

Conversation

@peterbarker

Copy link
Copy Markdown
Contributor

Summary

Removes 3.3% chance of Copter which has lost control/thrust of not deploying parachute due to beat frequency.

Classification & Testing (check all that apply and add your own)

  • 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

parachute_check() runs as a FAST_TASK, every main loop, but the baro_alt_m it reads is only refreshed by update_altitude(), a 10Hz scheduled task. The descent guard latched baro_alt_start_m from a sample and then, on the very next loop, compared it against a bit-identical copy of that same sample; >= was therefore true and the loss-of-control counter was reset before it could ever reach the second's worth of loops it needs to deploy.

Only a reading above the latched one is evidence that the vehicle is not falling. An equal reading carries no new information, and with a fresh, noisy baro exact float equality does not otherwise occur - so this changes only the stale case.

Found via the Copter Parachute autotest, which failed intermittently under load. An instrumented build showed the counter bouncing between 0 and 1 for eight consecutive seconds, reporting baro and latched altitude equal to the metre, on a vehicle demonstrably falling 150m with a steady 150 degree attitude error - every other condition for deployment satisfied throughout.

Measured by running 512 copies of that test at --parallel=32, twice, changing nothing but this comparison:

>=   17 failures in 512 runs
>     0 failures in 512 runs

with no other failures in either set.

@peterbarker

Copy link
Copy Markdown
Contributor Author

#33823 (fixing and running the ancient Parachute test) exposed this problem.

@tridge

tridge commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Automated review note - AI-generated (Claude), independently cross-checked by a second model and re-verified against the diff. Please sanity-check before acting.

Reviewed at head 8c0f20de93.

Verdict: APPROVE - no blockers.

  • VERIFIED - ArduCopter/crash_check.cpp:317 - Correct, and a more consequential fix than the one-character diff suggests. baro_alt_start_m is recorded when control_loss_count == 1, and the very next iteration compares against it. The loop runs at 400 Hz while the barometer updates at roughly 10–20 Hz, so consecutive iterations usually read the same altitude — and under the old >= that equality counted as “not falling” and reset control_loss_count to 0. The counter therefore almost never survived to reach the PARACHUTE_CHECK_TRIGGER_SEC threshold, effectively disabling the loss-of-control parachute release. Changing to > means only an actual climb resets it, which is what the comment always claimed. @peterbarker notes in the thread that #33823, which fixes and runs the ancient Parachute test, is what exposed this.

  • NOTE - ArduCopter/crash_check.cpp:320 - The pre-existing // To-Do: add check that the vehicle is actually falling remains immediately below, and is still the real gap — this change makes the existing weak proxy behave as documented rather than replacing it with a genuine descent test.

Full report, including what was checked and found clean: https://uav.tridgell.net/DevCallReviews/DevCallTopic/devcall_pr_reviews.html

} else if (baro_alt_m >= baro_alt_start_m) {
// exit if baro altitude change indicates we are climbing. Note
// that >= ("not falling") must not be used here
} else if (baro_alt_m > baro_alt_start_m) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change this to use the EKF altitude

@tridge

tridge commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

@rmackay9 I think we should merge this as the simple no-side-effects bug fix, then separately address using AHRS/GPS/baro alt in another PR? Maybe we want at least one alt source to be falling?

@rmackay9

Copy link
Copy Markdown
Contributor

I think if we're going to go to the trouble of fixing this then we should use the EKF. That will mean that we can leave the check as it was (e.g. ">=")

@peterbarker

Copy link
Copy Markdown
Contributor Author

I think if we're going to go to the trouble of fixing this then we should use the EKF. That will mean that we can leave the check as it was (e.g. ">=")

You might have deployed the parachute because of an EKF failsafe.

I agree that we should be using the EKF here - you might not even have a baro on board.

But what you're asking for is massive scope creep and would require vast amounts of testing and consideration of random failure paths. This is safety critical code and we need to be careful!

@rmackay9

rmackay9 commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Hi @peterbarker,

Re this having happened as part of an EKF failsafe, that's possible but it's intended to be because of a loss of control (e.g. motor or ESC failure).

I'm not sure that changing to use the EKF is any more dangerous than changing the ">=" to ">". Both open us up to "vast amounts of testing and consideration".

If we're really keen to stick with using the baro then I think the next best solution is to also save the baro update time so that we don't consume the same baro update twice. We ensure that when we do the >= test we're using a new baro reading.

@peterbarker peterbarker moved this from ReadyForDevCall to In Progress in Peter's ArduPilot 4.8 Queue Aug 20, 2026
@peterbarker
peterbarker force-pushed the pr-claude2/parachute-stale-baro branch from 8c0f20d to 77b224c Compare September 7, 2026 05:59
parachute_check() runs as a FAST_TASK, every main loop, but the
baro_alt_m it reads is only refreshed by update_altitude(), a 10Hz
scheduled task.  The descent guard latched baro_alt_start_m from a
sample and then, on the very next loop, compared it against a
bit-identical copy of that same sample; >= was therefore true and the
loss-of-control counter was reset before it could ever reach the
second's worth of loops it needs to deploy.

Only a reading above the latched one is evidence that the vehicle is
not falling.  An equal reading carries no new information, and with a
fresh, noisy baro exact float equality does not otherwise occur - so
this changes only the stale case.

Found via the Copter Parachute autotest, which failed intermittently
under load.  An instrumented build showed the counter bouncing between
0 and 1 for eight consecutive seconds, reporting baro and latched
altitude equal to the metre, on a vehicle demonstrably falling 150m
with a steady 150 degree attitude error - every other condition for
deployment satisfied throughout.

Measured by running 512 copies of that test at --parallel=32, twice,
changing nothing but this comparison:

    >=   17 failures in 512 runs
    >     0 failures in 512 runs

with no other failures in either set.
@peterbarker
peterbarker force-pushed the pr-claude2/parachute-stale-baro branch from 77b224c to d164176 Compare October 17, 2026 08:27
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Global Size Summary (Total Flash delta in bytes)

Built from cc56e44 rebased onto afeb9c4

Board Periph tracker blimp bootloader copter heli iofw plane rover sub
DrotekP3Pro _ _ _ * _ _ _ _ _ _
Durandal _ * * _ 0 0 _ * * *
Hitec-Airspeed * _ _ _ _ _ _ _ _ _
KakuteF7 _ * * _ * * _ * * *
MambaH743v4 _ * * _ 0 0 _ * * *
MatekF405-CAN _ _ _ * _ _ _ _ _ _
MatekF405 _ * * _ * * _ * * *
MatekH743-bdshot _ * * _ 0 0 _ * * *
Pixhawk1-1M _ * * _ 0 0 _ * * *
disco _ * * _ 0 0 _ * * *
f103-GPS * _ _ _ _ _ _ _ _ _

@AP-Review

AP-Review commented Oct 17, 2026 •

Copy link
Copy Markdown

Deprecated — see below for the updated review.

Previous review (2026-10-17)

Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting.
Verdict: COMMENT

Reviewed at head d164176466.
Full report: https://firmware.ardupilot.org/Tools/APReview/DevCallReviews/PRReviews/ardupilot/ardupilot/34033/1.html#pr34033

The fix at ArduCopter/crash_check.cpp:319 is correct and closes a real gap. With >=, a repeated 10 Hz baro sample reset the loss-of-control counter. In the review's SITL runs base missed the release in 3 of 8 single-motor failures and all 3 climb-then-failure runs; head released the chute in every one.

Two nonblocking suggestions:

  1. Please say in the PR description that a dead barometer is now a permanent 'stale' case. When every baro is unhealthy the altitude freezes, and the chute now releases (and by default disarms) after 1 s of >30 deg attitude error, where before it never released. That helps a vehicle that really is falling with a dead baro (SITL: base 0/3, head 3/3). It does not add a new false-release path, because a fresh noisy baro already lets a hovering vehicle with sustained error release within about 2 s. If you would rather stale data block the release, rmackay9's get_last_update() freshness check is the alternative.

  2. Copter.Parachute mostly passes with the old >= (12/13 here), so it would not reliably catch a regression. A case where the vehicle climbs before a single motor fails separated base and head 0/3 against 3/3 in a review-only probe. It might make a better regression test, but it has not been shown to be deterministic.

@AP-Review

AP-Review commented Oct 18, 2026 •

Copy link
Copy Markdown

Deprecated — see below for the updated review.

Previous review (2026-10-18)

Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting.
Verdict: REQUEST CHANGES

Reviewed at head e77c26a93f.
Full report: https://firmware.ardupilot.org/Tools/APReview/DevCallReviews/PRReviews/ardupilot/ardupilot/34033/2.html#pr34033

Reviewed at e77c26a, which adds the ParachuteStaleBaro autotest on top of the previously reviewed d164176. The crash_check.cpp change is unchanged.

The >= to > fix at ArduCopter/crash_check.cpp:319 is still correct, and the new test separates it cleanly: with every baro frozen, the old >= never released the chute in the review's SITL runs, and head released it every time.

One blocker, in the new test. ParachuteStaleBaro fails at head after the chute deploys, with "Test leaked parameters the suite could not revert: CHUTE_ENABLED 0.000000->1.000000". This looks like the red copter-tests2b job, though the CI log could not be read to confirm it. CHUTE_ENABLED is set inside the test's own context_push(). RC9 low then changes it to 0 in RAM only, so context_pop() skips the revert and its reboot reloads the stored 1. The set_parameter("CHUTE_ENABLED", 1) after it runs in the outer context and records 1 as the original. _Parachute avoids this by setting CHUTE_ENABLED outside its inner contexts. Moving that set_parameter to just before context_pop() fixes it. With that change the test passed in SITL, including Parachute,ParachuteStaleBaro,MANUAL_CONTROL in suite order, and still fails with >=.

Minor, nonblocking: it would still help to say in the PR description that a baro that goes unhealthy also freezes the altitude (AP_Baro only updates altitude for healthy sensors), so a dead baro is now a permanent stale case that allows a release after 1 s of more than 30 deg attitude error.

Copter.Parachute catches the stale-altitude bug only by luck: its
loss-of-control subtest passes with the old ">=" in 12 runs of 13 here,
and measuring the real rate took 512 runs to find 17 failures.  A test
which fails 3% of the time does not defend the fix.

Freeze every simulated barometer instead.  parachute_check() runs every
main loop against an altitude a 10Hz task refreshes, so most loops
already see a bit-identical copy of the previous sample; a frozen baro
is that staleness made permanent, and the outcome deterministic.  With
the descent guard treating an unchanged altitude as evidence of not
falling the counter can never run and the chute never deploys; with it
testing only for a climb, the release happens as it should.

The vehicle climbs on a working barometer first - the frozen altitude
has to be a plausible one to fall from - and the chute is armed before
the motors are failed.
@peterbarker
peterbarker force-pushed the pr-claude2/parachute-stale-baro branch from e77c26a to cc56e44 Compare October 18, 2026 03:15
@AP-Review

Copy link
Copy Markdown

Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting.
Verdict: ACCEPT

Reviewed at head cc56e44d8d.
Full report: https://firmware.ardupilot.org/Tools/APReview/DevCallReviews/PRReviews/ardupilot/ardupilot/34033/3.html#pr34033

Reviewed at cc56e44. Since e77c26a the only change is in the autotest: the CHUTE_ENABLED reset now runs before context_pop(). crash_check.cpp is unchanged.

The previous blocker is fixed. With the reset at arducopter.py:6988, ParachuteStaleBaro passed in SITL with no parameter leak, both alone and in suite order with Parachute and MANUAL_CONTROL. CI is green, including copter-tests2b. The >= to > fix at crash_check.cpp:319 is still correct, and the test still separates it: a build with >= restored never deployed the chute.

For maintainers only, not a request to the author: a baro that goes unhealthy also freezes the altitude, so a dead baro now permits a release after 1 s of more than 30 deg attitude error. Whether stale data should block a release (the EKF or baro-freshness alternatives) is a design call for maintainers.

@peterbarker peterbarker moved this from In Progress to ReadyForDevCall in Peter's ArduPilot 4.8 Queue Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status
Status: ReadyForDevCall

Development

Successfully merging this pull request may close these issues.

5 participants