Problem
INAV supports dead reckoning — continuing navigation after GPS loss using IMU and barometer. However, isProbablyStillFlying() for fixed-wing depends on isGPSHeadingValid(), which requires GPS fix, ≥6 satellites, and ground speed ≥300 cm/s. During dead reckoning, GPS is already absent, so this returns false unconditionally.
The consequence: a pilot flying on dead reckoning who accidentally disarms cannot use IN_FLIGHT_EMERG_REARM to recover. The emergency rearm path is inadvertently blocked by the same GPS failure that triggered dead reckoning in the first place.
This is one instance of a wider pattern. isGPSHeadingValid() appears nine times in the codebase; investigation identified four where it is being used as a flight proxy rather than a GPS protocol check:
| File |
Use |
Problem |
navigation.c:3604 |
isProbablyStillFlying() FW path |
Returns false during dead reckoning |
navigation_fixedwing.c:767 |
isFixedWingFlying() — landing detector |
GPS gate overrides estimator velXY/alt signals already present |
servos.c:643 |
Enable mixer flying mode |
Marked // TODO: proper flying detection |
navigation.c:4686 |
Gate launch mode activation |
GPS failure mid-flight allows launch mode to activate while airborne |
The remaining five call sites correctly depend on GPS data specifically (IMU attitude fusion, wind estimation, RTH trackback, launch detection, position estimation) and are not proposed for change.
Two approaches
Option A — isPosEstimatorHeadingValid() (surgical replacement)
A direct drop-in for isGPSHeadingValid() at the four call sites. Same proxy logic — valid heading implies forward motion implies flight — but sourced from the estimator's fused velocity rather than raw GPS:
bool isPosEstimatorHeadingValid(void)
{
return isGPSHeadingValid() ||
(posControl.actualState.velXY > HEADING_VALID_MIN_SPEED_CMS);
}
posControl.actualState.velXY is already computed by the position estimator from fused IMU/baro/GPS state. Under GPS loss it continues to be valid from IMU integration; it does not go stale when GPS drops. HEADING_VALID_MIN_SPEED_CMS would match the existing GPS threshold (300 cm/s) for consistency.
Change at each call site: replace isGPSHeadingValid() with isPosEstimatorHeadingValid(). No behavioural change when GPS is healthy.
Tradeoffs:
- Minimal blast radius — one new function, four substitutions
- Preserves the existing heading-as-flight-proxy pattern rather than fixing it
- Does not address the
servos.c TODO
- If the IMU velocity estimate drifts significantly during dead reckoning, the threshold may need tuning
Option B — isEstimatedFlyingConfident() (architectural fix)
A new flight-detection function that explicitly uses baro altitude above takeoff as the primary GPS-independent signal, with estimator velocity and airspeed as supporting signals:
bool isEstimatedFlyingConfident(void)
{
if (STATE(MULTIROTOR)) {
return posControl.actualState.velXY > MC_LAND_CHECK_VEL_XY_MOVING ||
averageAbsGyroRates() > 4.0f;
} else {
bool altCheck = fabsf(posControl.actualState.abs.pos.z - getTakeoffAltitude()) > 500.0f;
bool velCheck = posControl.actualState.velXY > 350.0f;
#ifdef USE_PITOT
bool airspeedCheck = sensors(SENSOR_PITOT) && pitotIsHealthy() &&
getAirspeedEstimate() > 350.0f;
#else
bool airspeedCheck = false;
#endif
return isGPSHeadingValid() || altCheck || (velCheck && airspeedCheck);
}
}
Multirotor path is unchanged from current behaviour. Fixed-wing path adds baro altitude and estimator velocity/airspeed as fallbacks when GPS fails.
Tradeoffs:
- Semantically correct — answers "are we flying?" rather than "do we have a heading?"
- Fixes the
servos.c TODO properly
- Introduces threshold constants that will need justification and possible tuning per airframe
- Larger change; more surface area for review
Comparison
|
Option A |
Option B |
| Change size |
~5 lines + 4 substitutions |
~20 lines + 4 substitutions |
| Concept introduced |
Better heading proxy |
New flight-detection concept |
servos.c TODO |
Not addressed |
Addressed |
| Threshold tuning needed |
No (reuses existing 300 cm/s) |
Yes (altitude threshold, velocity threshold) |
| Regression risk |
Very low |
Low |
| Dead reckoning fix |
Yes |
Yes |
Questions for discussion
- Option A or Option B — or a different approach entirely?
- For Option A: is 300 cm/s the right minimum estimator velocity for heading validity, or should it be lower given IMU velocity is less precise than GPS?
- For Option B: is 5 m the right altitude threshold above takeoff? Should it be configurable?
- Are there fixed-wing flight profiles where dead reckoning velocity estimate would be unreliable enough to cause false positives (e.g. prolonged soaring with engine off)?
Problem
INAV supports dead reckoning — continuing navigation after GPS loss using IMU and barometer. However,
isProbablyStillFlying()for fixed-wing depends onisGPSHeadingValid(), which requires GPS fix, ≥6 satellites, and ground speed ≥300 cm/s. During dead reckoning, GPS is already absent, so this returns false unconditionally.The consequence: a pilot flying on dead reckoning who accidentally disarms cannot use
IN_FLIGHT_EMERG_REARMto recover. The emergency rearm path is inadvertently blocked by the same GPS failure that triggered dead reckoning in the first place.This is one instance of a wider pattern.
isGPSHeadingValid()appears nine times in the codebase; investigation identified four where it is being used as a flight proxy rather than a GPS protocol check:navigation.c:3604isProbablyStillFlying()FW pathnavigation_fixedwing.c:767isFixedWingFlying()— landing detectorservos.c:643// TODO: proper flying detectionnavigation.c:4686The remaining five call sites correctly depend on GPS data specifically (IMU attitude fusion, wind estimation, RTH trackback, launch detection, position estimation) and are not proposed for change.
Two approaches
Option A —
isPosEstimatorHeadingValid()(surgical replacement)A direct drop-in for
isGPSHeadingValid()at the four call sites. Same proxy logic — valid heading implies forward motion implies flight — but sourced from the estimator's fused velocity rather than raw GPS:posControl.actualState.velXYis already computed by the position estimator from fused IMU/baro/GPS state. Under GPS loss it continues to be valid from IMU integration; it does not go stale when GPS drops.HEADING_VALID_MIN_SPEED_CMSwould match the existing GPS threshold (300 cm/s) for consistency.Change at each call site: replace
isGPSHeadingValid()withisPosEstimatorHeadingValid(). No behavioural change when GPS is healthy.Tradeoffs:
servos.cTODOOption B —
isEstimatedFlyingConfident()(architectural fix)A new flight-detection function that explicitly uses baro altitude above takeoff as the primary GPS-independent signal, with estimator velocity and airspeed as supporting signals:
Multirotor path is unchanged from current behaviour. Fixed-wing path adds baro altitude and estimator velocity/airspeed as fallbacks when GPS fails.
Tradeoffs:
servos.cTODO properlyComparison
servos.cTODOQuestions for discussion