Add wind estimation for ExternalAHRS - #34410
peterbarker wants to merge 9 commits into
Conversation
estimate_wind's first calls merely seed its filter history; until it has actually blended a measurement, the zero _wind is not an estimate at all. Record when _wind has been updated so backends can publish an honest wind_valid. DCM deliberately does not consult this flag: it has always reported its initial zero wind as a valid estimate, and this refactor does not change existing behaviour.
ExternalAHRS provides a velocity and attitude solution but no wind estimate, so previously a vehicle running an external AHRS reported no wind. Feed the external velocity and attitude into the same wind-triangle estimator DCM uses and publish the result. The estimator is driven from get_results, reusing the velocity and attitude just captured for publication, and is fed once per new external sample (estimate_wind additionally rate-limits internally). The estimate is only produced, and only reported valid, while wind estimation is enabled (i.e. on Plane); other vehicles are unaffected. It is only valid once the estimator has actually produced an estimate, and is invalidated while the external source is unhealthy, since it may then be arbitrarily stale; a merely-old estimate from a healthy source stays valid, as a stale wind is still useful when dead-reckoning.
Reports the validity of ahrs:get_wind() as a NAMED_VALUE_FLOAT for autotest consumption; validity is not otherwise visible in telemetry.
Flies a VectorNav external-AHRS SITL in LOITER with a simulated wind and asserts the WIND estimate converges, exercising the wind triangle driven from ExternalAHRS velocity and attitude. The WIND message is sent whether or not the estimate is valid, so a small script reports the validity of ahrs:get_wind(); the test also requires the estimate be reported valid.
Stamp state.last_velocity_update_us in every driver which publishes a velocity, and provide an accessor returning the velocity and its timestamp captured under a single semaphore acquisition. Velocity freshness cannot be inferred from last_location_update_us: several devices (e.g. InertialLabs) deliver velocity and position in separate messages, so the position timestamp can keep advancing while the velocity stream has stalled.
When SIM_ILABS_NO_VEL is set, splice the VELOCITIES message out of the transmitted packet - the message-type list entry and the payload bytes - while attitude and position messages continue to flow. Allows testing of how consumers handle a stalled velocity stream on a device which delivers velocity and position in separate messages.
The wind feed was keyed to last_location_update_us, but some devices (e.g. InertialLabs) deliver velocity and position in separate messages, so a stalled velocity stream would be re-fused on every new position message, corrupting the persistent wind-filter state while wind_valid remained true. Key the feed to the velocity's own timestamp instead, captured atomically with the velocity under a single semaphore acquisition; a stalled velocity stream now stops wind estimation entirely.
Stall the simulated InertialLabs velocity stream while turning in LOITER. The stale velocity flags in the filter status demote the AHRS to DCM, but the External backend continues to run, so the wind estimate must match the pre-stall estimate the moment the external backend is re-selected. With wind feeding keyed to last_location_update_us the estimate is corrupted by the stalled velocity and fails that check.
…ator The early return on invalid attitude already guarantees this, so no behaviour changes; estimate_wind requires a unit fuselage direction, and a freshly-zeroed dcm_matrix must never reach it, so make the requirement local to the feed rather than a consequence of control flow far above it.
|
Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report, with everything that was checked: https://uav.tridgell.net/DevCallReviews/2026_09_16_AIReview/devcall_pr_reviews.html#pr34410 Verdict: COMMENT — 1 bug, 1 issue below. The design is sound and the frame/sign/unit work is right — no new conversion is introduced, Bugs
Issues
4 further minor notes (style, coverage, pre-existing items) are in the report rather than here, so they don't bury the above. Independent cold pass: no verified defects, APPROVE. The primary read's BUG was proven by a SITL probe and a source mutation, so it is retained despite the disagreement. Reviewed by: Claude (full read of the diff, thread and surrounding source) + an independent Codex cold pass that was not shown these findings. Findings marked both reviewers were reproduced independently twice. |
Summary
ExternalAHRS backends now estimate wind, feeding the shared wind-triangle estimator from the external device's velocity and attitude. Builds on the estimator lift merged in #33946.
Classification & Testing (check all that apply and add your own)
Two new autotests.
ExternalAHRSWindEstimateflies a VectorNav SITL in LOITER with a simulated wind and requires the WIND estimate to converge on it and to be reported valid — validity is not visible in the WIND message (Plane sends the vector regardless), so a small test script reportsahrs:get_wind()'s validity over a NAMED_VALUE_FLOAT.ExternalAHRSWindEstimateVelocityStallstalls just the velocity messages of a simulated InertialLabs (velocity and position are separate messages on that device) while turning in LOITER, and requires the wind estimate to match the pre-stall estimate the moment the External backend is re-selected. This test was proven to discriminate: with wind feeding keyed to the position timestamp instead of the velocity timestamp, the stalled velocity is re-fused every position message and the corrupted estimate fails the check (12.7° off in an A/B run); with this series it survives bit-identical.Every commit builds individually;
WindEstimates,DeadreckoningandDeadreckoningNoAirSpeedalso pass at the tip (no change to DCM behaviour).Description
ExternalAHRS provides a velocity and attitude solution but no wind estimate, so a vehicle flying on an external AHRS reports no wind. This series feeds the external velocity and attitude into the same wind-triangle estimator DCM uses (lifted to
AP_AHRS_Backendin #33946) and publishes the result.Commit by commit:
estimate_wind's first calls merely seed its filter history; until it has actually blended a measurement the zero_windis not an estimate at all. A new flag lets backends publish an honestwind_valid. DCM deliberately does not consult it: DCM has always reported its initial zero wind as valid, and this series does not change DCM behaviour.get_results(), reusing the velocity and attitude just captured for publication, once per new external sample (estimate_windadditionally rate-limits to 10Hz internally).wind_validrequires wind estimation enabled (i.e. Plane — other vehicles are unaffected), an estimate actually produced, and the source healthy: an unhealthy source's estimate may be arbitrarily stale, while a merely-old estimate from a healthy source stays valid — a stale wind is still useful when dead-reckoning.AP_ExternalAHRS) — every driver publishing a velocity stampsstate.last_velocity_update_us, and aget_velocity_NED()overload returns the velocity and its timestamp captured under a single semaphore acquisition.last_location_update_us, but on devices delivering velocity and position in separate messages a stalled velocity stream would be re-fused on every new position message, corrupting the persistent filter state whilewind_validremained true. The feed now keys on the velocity's own timestamp, so a stalled velocity stream stops wind estimation entirely.SIM_ILABS_NO_VELsplices the VELOCITIES message out of the simulated packet while attitude and position continue, enabling the stall autotest above.estimate_windrequires a unit fuselage direction, so the requirement is made local to the feed rather than a consequence of distant control flow.Accepted properties, for the record:
wind_validinstead.Follow-up enabled but deliberately not included: routing ExternalAHRS synthetic airspeed to its own backend (needs both the frontend dispatch switch and an External producer for
_last_airspeed_TAS; synthetic airspeed still comes from DCM in exactly the cases it does today).This PR was AI-assisted