function
Estimation.StrapdownINS.ESKF.step
Predict, select absolute aiding, and fuse independent flow, height and heading
Public declarations
Explicit source declarations; dimensions, modifications and defaults are shown as written, without evaluation.
| Role | Name | Type | Dimensions / attributes / default | Description |
|---|---|---|---|---|
| input | initializedPrevious | Boolean | | |
| input | previous | State | | |
| input | reset | Boolean | | |
| input | imu | Avionics.ImuSample | | |
| input | mocap | Avionics.MocapSample | | |
| input | gps | Avionics.GpsSample | | |
| input | magnetometer | Avionics.MagnetometerSample | | |
| input | barometer | Avionics.BarometerSample | | |
| input | opticalFlow | Avionics.OpticalFlowSample | | |
| input | gravityWorldEnu_m_s2 | Real | [3] | |
| input | dt | Real | | |
| input | tuning | Tuning | | |
| input | consecutiveRejectionsPrevious | Integer | | |
| input | rejectionElapsedPrevious_s | Real | | |
| input | mocapRejectionsPrevious | Integer | | |
| input | gpsRejectionsPrevious | Integer | | |
| input | opticalFlowRejectionsPrevious | Integer | | |
| input | anchorSourcePrevious | Integer | | Latched anchor from the previous tick. Latched, not momentary: a source going quiet between its own samples must not read as a change |
| input | mocapStalePrevious_s | Real | | |
| input | gpsStalePrevious_s | Real | | |
| input | opticalFlowStalePrevious_s | Real | | |
| input | imuAngularVelocityHeldPrevious_rad_s | Real | [3] | |
| input | imuSpecificForceHeldPrevious_m_s2 | Real | [3] | |
| input | imuTimestampHeldPrevious_s | Real | | |
| input | mocapTimestampConsumedPrevious_s | Real | | |
| input | gpsTimestampConsumedPrevious_s | Real | | |
| input | magnetometerTimestampConsumedPrevious_s | Real | | |
| input | barometerTimestampConsumedPrevious_s | Real | | |
| input | opticalFlowTimestampConsumedPrevious_s | Real | | |
| input | quietElapsedPrevious_s | Real | | |
| input | alignmentWaitPrevious_s | Real | | |
| input | pseudoPositionHoldPrevious_m | Real | [3] | |
| input | alignmentSourcePrevious | Integer | | |
| input | alignmentSpecificForcePrevious_m_s2 | Real | [3] | |
| input | quietAngularRatePrevious_rad_s | Real | [3] | |
| input | vehicleAtRest | Boolean | = false | |
| output | positionNext | Real | [3] | |
| output | velocityNext | Real | [3] | |
| output | quaternionNext | Real | [4] | |
| output | gyroscopeBiasNext | Real | [3] | |
| output | accelerometerBiasNext | Real | [3] | |
| output | covarianceNext | Covariance | | |
| output | initializedNext | Boolean | | |
| output | predictionAccepted | Boolean | | |
| output | mocapCorrectionAccepted | Boolean | | |
| output | gpsPositionCorrectionAccepted | Boolean | | |
| output | gpsVelocityCorrectionAccepted | Boolean | | |
| output | magnetometerCorrectionAccepted | Boolean | | |
| output | barometerCorrectionAccepted | Boolean | | |
| output | opticalFlowCorrectionAccepted | Boolean | | |
| output | consecutiveRejectionsNext | Integer | | |
| output | rejectionElapsedNext_s | Real | | Wall-clock time the anchor source has held without moving the state. Advanced on estimator ticks, not on aiding attempts and not on the sensors` valid duty cycle. Cleared when the anchor accepts and when a genuinely different source takes over the anchor; FROZEN, not cleared, while no source is delivering |
| output | recoveryStage | Integer | | Estimation.StrapdownINS.Recovery* code for the automatic recovery ladder driven by sustained rejection |
| output | correctionOutcome | Integer | | Estimation.StrapdownINS.Correction* code for this tick's attempted aiding correction |
| output | correctionSource | Integer | | Estimation.StrapdownINS.Source* code the outcome refers to |
| output | normalizedInnovationSquared | Real | | NIS for this tick's attempted correction, or zero when none was attempted |
| output | estimateValid | Boolean | | Whether the published navigation estimate may be consumed |
| output | reseeded | Boolean | | True on the tick the automatic recovery ladder re-seeded the state from a fresh anchor sample after a sustained rejection window |
| output | mocapRejectionsNext | Integer | | Consecutive rejections of the mocap source alone |
| output | gpsRejectionsNext | Integer | | Consecutive rejections of the GPS source alone |
| output | opticalFlowRejectionsNext | Integer | | Consecutive rejections of the optical-flow source alone |
| output | anchorSourceNext | Integer | | Estimation.StrapdownINS.Source* code of the anchor in force |
| output | mocapStaleNext_s | Real | | |
| output | gpsStaleNext_s | Real | | |
| output | opticalFlowStaleNext_s | Real | | |
| output | imuAngularVelocityHeldNext_rad_s | Real | [3] | IMU angular velocity to PUBLISH: the current sample when it is finite, otherwise the last finite one |
| output | imuSpecificForceHeldNext_m_s2 | Real | [3] | IMU specific force to PUBLISH, same hold rule |
| output | imuTimestampHeldNext_s | Real | | IMU timestamp to PUBLISH, same hold rule |
| output | imuPayloadHeld | Boolean | | True on a tick whose published IMU-derived outputs are held from an earlier sample because the current one was not finite |
| output | mocapTimestampConsumedNext_s | Real | | |
| output | gpsTimestampConsumedNext_s | Real | | |
| output | magnetometerTimestampConsumedNext_s | Real | | |
| output | barometerTimestampConsumedNext_s | Real | | |
| output | opticalFlowTimestampConsumedNext_s | Real | | |
| output | alignmentWaitNext_s | Real | | Time with a usable IMU spent waiting for the alignment gate; zero once aligned |
| output | quietElapsedNext_s | Real | | Unbroken time the IMU has reported a quasi-static vehicle: specific force within tolerance of gravity and angular rate below the limit |
| output | pseudoPositionHoldNext_m | Real | [3] | Position the synthetic hold-position measurement is taken at: the current estimate while an anchor is live, frozen when it drops |
| output | alignmentSource | Integer | | Estimation.StrapdownINS.Alignment* code of the alignment in force |
| output | alignmentSpecificForce_m_s2 | Real | [3] | Low-pass specific force the quasi-static test reads and the initial alignment levels on; the raw sample when no filter is configured |
| output | quietAngularRateNext_rad_s | Real | [3] | Low-pass angular rate the quasi-static test reads |
| output | pseudoPositionCorrectionAccepted | Boolean | | |
| output | zeroVelocityCorrectionAccepted | Boolean | | |
| output | stationaryImuCorrectionAccepted | Boolean | | |
| output | covarianceRootNext | Covariance | | |
| output | barometerBiasCrossCovarianceNext | Real | [TangentLength] | |
| output | barometerBiasNext_m | Real | | |
| output | barometerBiasVarianceNext_m2 | Real | |
Modelica declarations and implementation
Show parameters, interfaces and source
Parameters, inputs, outputs, defaults and equations from the source file.
function step
"Predict, select absolute aiding, and fuse independent flow, height and heading"
input Boolean initializedPrevious;
input State previous;
input Boolean reset;
input Avionics.ImuSample imu;
input Avionics.MocapSample mocap;
input Avionics.GpsSample gps;
input Avionics.MagnetometerSample magnetometer;
input Avionics.BarometerSample barometer;
input Avionics.OpticalFlowSample opticalFlow;
input Real gravityWorldEnu_m_s2[3];
input Real dt;
input Tuning tuning;
input Integer consecutiveRejectionsPrevious;
input Real rejectionElapsedPrevious_s;
input Integer mocapRejectionsPrevious;
input Integer gpsRejectionsPrevious;
input Integer opticalFlowRejectionsPrevious;
input Integer anchorSourcePrevious
"Latched anchor from the previous tick. Latched, not momentary: a
source going quiet between its own samples must not read as a change";
input Real mocapStalePrevious_s;
input Real gpsStalePrevious_s;
input Real opticalFlowStalePrevious_s;
input Real imuAngularVelocityHeldPrevious_rad_s[3];
input Real imuSpecificForceHeldPrevious_m_s2[3];
input Real imuTimestampHeldPrevious_s;
input Real mocapTimestampConsumedPrevious_s;
input Real gpsTimestampConsumedPrevious_s;
input Real magnetometerTimestampConsumedPrevious_s;
input Real barometerTimestampConsumedPrevious_s;
input Real opticalFlowTimestampConsumedPrevious_s;
input Real quietElapsedPrevious_s;
input Real alignmentWaitPrevious_s;
input Real pseudoPositionHoldPrevious_m[3];
input Integer alignmentSourcePrevious;
input Real alignmentSpecificForcePrevious_m_s2[3];
input Real quietAngularRatePrevious_rad_s[3];
input Boolean vehicleAtRest = false;
output Real positionNext[3];
output Real velocityNext[3];
output Real quaternionNext[4];
output Real gyroscopeBiasNext[3];
output Real accelerometerBiasNext[3];
output Covariance covarianceNext;
output Boolean initializedNext;
output Boolean predictionAccepted;
output Boolean mocapCorrectionAccepted;
output Boolean gpsPositionCorrectionAccepted;
output Boolean gpsVelocityCorrectionAccepted;
output Boolean magnetometerCorrectionAccepted;
output Boolean barometerCorrectionAccepted;
output Boolean opticalFlowCorrectionAccepted;
output Integer consecutiveRejectionsNext;
output Real rejectionElapsedNext_s
"Wall-clock time the anchor source has held without moving the state.
Advanced on estimator ticks, not on aiding attempts and not on the
sensors` valid duty cycle. Cleared when the anchor accepts and when a
genuinely different source takes over the anchor; FROZEN, not
cleared, while no source is delivering";
output Integer recoveryStage
"Estimation.StrapdownINS.Recovery* code for the automatic
recovery ladder driven by sustained rejection";
output Integer correctionOutcome
"Estimation.StrapdownINS.Correction* code for this tick's
attempted aiding correction";
output Integer correctionSource
"Estimation.StrapdownINS.Source* code the outcome refers to";
output Real normalizedInnovationSquared
"NIS for this tick's attempted correction, or zero when none was attempted";
output Boolean estimateValid
"Whether the published navigation estimate may be consumed";
output Boolean reseeded
"True on the tick the automatic recovery ladder re-seeded the state
from a fresh anchor sample after a sustained rejection window";
output Integer mocapRejectionsNext
"Consecutive rejections of the mocap source alone";
output Integer gpsRejectionsNext
"Consecutive rejections of the GPS source alone";
output Integer opticalFlowRejectionsNext
"Consecutive rejections of the optical-flow source alone";
output Integer anchorSourceNext
"Estimation.StrapdownINS.Source* code of the anchor in force";
output Real mocapStaleNext_s;
output Real gpsStaleNext_s;
output Real opticalFlowStaleNext_s;
output Real imuAngularVelocityHeldNext_rad_s[3]
"IMU angular velocity to PUBLISH: the current sample when it is
finite, otherwise the last finite one";
output Real imuSpecificForceHeldNext_m_s2[3]
"IMU specific force to PUBLISH, same hold rule";
output Real imuTimestampHeldNext_s
"IMU timestamp to PUBLISH, same hold rule";
output Boolean imuPayloadHeld
"True on a tick whose published IMU-derived outputs are held from an
earlier sample because the current one was not finite";
output Real mocapTimestampConsumedNext_s;
output Real gpsTimestampConsumedNext_s;
output Real magnetometerTimestampConsumedNext_s;
output Real barometerTimestampConsumedNext_s;
output Real opticalFlowTimestampConsumedNext_s;
output Real alignmentWaitNext_s
"Time with a usable IMU spent waiting for the alignment gate; zero once
aligned";
output Real quietElapsedNext_s
"Unbroken time the IMU has reported a quasi-static vehicle: specific
force within tolerance of gravity and angular rate below the limit";
output Real pseudoPositionHoldNext_m[3]
"Position the synthetic hold-position measurement is taken at: the
current estimate while an anchor is live, frozen when it drops";
output Integer alignmentSource
"Estimation.StrapdownINS.Alignment* code of the alignment in force";
output Real alignmentSpecificForce_m_s2[3]
"Low-pass specific force the quasi-static test reads and the initial
alignment levels on; the raw sample when no filter is configured";
output Real quietAngularRateNext_rad_s[3]
"Low-pass angular rate the quasi-static test reads";
output Boolean pseudoPositionCorrectionAccepted;
output Boolean zeroVelocityCorrectionAccepted;
output Boolean stationaryImuCorrectionAccepted;
output Covariance covarianceRootNext;
output Real barometerBiasCrossCovarianceNext[TangentLength];
output Real barometerBiasNext_m;
output Real barometerBiasVarianceNext_m2;
protected
State working;
Boolean correctionAttempted;
Boolean correctionAccepted;
Boolean aidingLive;
Boolean inflateCovarianceNow;
Boolean aidingDivergent;
Boolean reseedConfigured;
Boolean reseedNow;
Integer reseedSource;
Boolean anchorExclusive;
Boolean imuUsable;
Boolean imuPayloadFinite;
Boolean ladderConfigured;
Integer anchorPresent;
Boolean mocapSeedUsable;
Boolean gpsSeedUsable;
Boolean magnetometerSeedUsable;
Boolean mocapNew;
Boolean gpsNew;
Boolean magnetometerNew;
Boolean barometerNew;
Boolean opticalFlowNew;
Real mocapSeedQuaternionNorm;
Real specificForceMagnitude;
Real angularRateMagnitude;
Real gravityMagnitude;
Real sampleSpecificForce_m_s2[3];
Real sampleAngularRate_rad_s[3];
Real quietFilterGain;
Boolean quietFilterSeeded;
Boolean imuQuiet;
Boolean alignmentGateConfigured;
Boolean alignmentReady;
Boolean alignmentTimedOut;
Boolean alignmentPending;
algorithm
predictionAccepted := false;
mocapCorrectionAccepted := false;
gpsPositionCorrectionAccepted := false;
gpsVelocityCorrectionAccepted := false;
magnetometerCorrectionAccepted := false;
barometerCorrectionAccepted := false;
opticalFlowCorrectionAccepted := false;
correctionAttempted := false;
correctionAccepted := false;
correctionOutcome := CorrectionNotAttempted;
correctionSource := SourceNone;
normalizedInnovationSquared := 0.0;
reseeded := false;
pseudoPositionCorrectionAccepted := false;
zeroVelocityCorrectionAccepted := false;
stationaryImuCorrectionAccepted := false;
alignmentSource := alignmentSourcePrevious;
alignmentSpecificForce_m_s2 := alignmentSpecificForcePrevious_m_s2;
pseudoPositionHoldNext_m := pseudoPositionHoldPrevious_m;
reseedNow := false;
reseedSource := SourceNone;
consecutiveRejectionsNext := 0;
rejectionElapsedNext_s := 0.0;
// A commanded reset and first initialization clear every source's
// history: the state they describe no longer exists.
mocapRejectionsNext := 0;
gpsRejectionsNext := 0;
opticalFlowRejectionsNext := 0;
mocapTimestampConsumedNext_s := mocapTimestampConsumedPrevious_s;
gpsTimestampConsumedNext_s := gpsTimestampConsumedPrevious_s;
magnetometerTimestampConsumedNext_s :=
magnetometerTimestampConsumedPrevious_s;
barometerTimestampConsumedNext_s := barometerTimestampConsumedPrevious_s;
opticalFlowTimestampConsumedNext_s :=
opticalFlowTimestampConsumedPrevious_s;
imuPayloadFinite := imu.valid and imuSampleFinite(imu);
// Cross-clock delivery is level-triggered: the producer holds the most
// recent packet, and timestamp novelty makes consumption exactly once.
imuUsable := imuPayloadFinite
and imu.timestamp_s > imuTimestampHeldPrevious_s + 1.0e-9;
sampleSpecificForce_m_s2 := if imu.integrationTime_s > 1.0e-6
then imu.deltaVelocityBodyFlu_m_s / imu.integrationTime_s
else imu.specificForceBodyFlu_m_s2;
sampleAngularRate_rad_s := if imu.integrationTime_s > 1.0e-6
then imu.deltaAngleBodyFlu_rad / imu.integrationTime_s
else imu.angularVelocityBodyFlu_rad_s;
quietFilterGain := if tuning.quietFilterTimeConstant_s > 0.0
and tuning.quietFilterTimeConstant_s < FiniteMagnitudeLimit
then min(1.0, dt / tuning.quietFilterTimeConstant_s) else 1.0;
quietFilterSeeded := alignmentSpecificForcePrevious_m_s2
* alignmentSpecificForcePrevious_m_s2 > 0.0;
if not imuUsable then
alignmentSpecificForce_m_s2 := alignmentSpecificForcePrevious_m_s2;
quietAngularRateNext_rad_s := quietAngularRatePrevious_rad_s;
elseif not quietFilterSeeded then
alignmentSpecificForce_m_s2 := sampleSpecificForce_m_s2;
quietAngularRateNext_rad_s := sampleAngularRate_rad_s;
else
alignmentSpecificForce_m_s2 := alignmentSpecificForcePrevious_m_s2
+ quietFilterGain
* (sampleSpecificForce_m_s2 - alignmentSpecificForcePrevious_m_s2);
quietAngularRateNext_rad_s := quietAngularRatePrevious_rad_s
+ quietFilterGain
* (sampleAngularRate_rad_s - quietAngularRatePrevious_rad_s);
end if;
specificForceMagnitude := sqrt(alignmentSpecificForce_m_s2
* alignmentSpecificForce_m_s2);
angularRateMagnitude := sqrt(quietAngularRateNext_rad_s
* quietAngularRateNext_rad_s);
gravityMagnitude := sqrt(gravityWorldEnu_m_s2 * gravityWorldEnu_m_s2);
imuQuiet := imuUsable
and abs(specificForceMagnitude - gravityMagnitude)
<= tuning.quietSpecificForceTolerance_m_s2
and angularRateMagnitude <= tuning.quietAngularRateLimit_rad_s;
// The window is a property of the inertial stream alone: a commanded
// reset re-aligns from it but does not restart it, and a consumer that
// holds reset asserted until the filter reports initialized must still
// see the gate open. A tick that fails the test costs the window one
// tick rather than all of it, so an isolated noise excursion delays the
// gate instead of restarting it; sustained motion still drains it.
quietElapsedNext_s := if not imuQuiet
then max(0.0, quietElapsedPrevious_s - dt)
else quietElapsedPrevious_s + dt;
alignmentGateConfigured := tuning.initialAlignmentWindow_s > 0.0
and tuning.initialAlignmentWindow_s < FiniteMagnitudeLimit;
mocapNew := mocap.valid
and abs(mocap.timestamp_s) < FiniteMagnitudeLimit
and mocap.timestamp_s > mocapTimestampConsumedPrevious_s + 1.0e-9;
gpsNew := gps.valid
and abs(gps.timestamp_s) < FiniteMagnitudeLimit
and gps.timestamp_s > gpsTimestampConsumedPrevious_s + 1.0e-9;
magnetometerNew := magnetometer.valid
and abs(magnetometer.timestamp_s) < FiniteMagnitudeLimit
and magnetometer.timestamp_s
> magnetometerTimestampConsumedPrevious_s + 1.0e-9;
barometerNew := barometer.valid
and abs(barometer.timestamp_s) < FiniteMagnitudeLimit
and barometer.timestamp_s > barometerTimestampConsumedPrevious_s + 1.0e-9;
opticalFlowNew := opticalFlow.valid
and abs(opticalFlow.timestamp_s) < FiniteMagnitudeLimit
and opticalFlow.timestamp_s
> opticalFlowTimestampConsumedPrevious_s + 1.0e-9;
// Initialization payloads must satisfy the same finite-data contract as
// correction payloads before they can seed persistent state.
mocapSeedUsable := mocap.valid;
for axis in 1:3 loop
mocapSeedUsable := mocapSeedUsable
and abs(mocap.positionWorldEnu_m[axis]) < FiniteMagnitudeLimit;
end for;
for component in 1:4 loop
mocapSeedUsable := mocapSeedUsable
and abs(mocap.quaternionWorldBody[component]) < FiniteMagnitudeLimit;
end for;
// Normalizability, not just finiteness: a finite but vanishing
// quaternion survives every magnitude check and then becomes a
// full-scale garbage rotation when initialize() normalizes it.
mocapSeedQuaternionNorm := mocap.quaternionWorldBody
* mocap.quaternionWorldBody;
mocapSeedUsable := mocapSeedUsable
and sqrt(mocapSeedQuaternionNorm) >= MinimumSeedQuaternionNorm;
gpsSeedUsable := gps.valid and gps.positionValid;
for axis in 1:3 loop
gpsSeedUsable := gpsSeedUsable
and abs(gps.positionWorldEnu_m[axis]) < FiniteMagnitudeLimit;
end for;
magnetometerSeedUsable := magnetometer.valid;
for axis in 1:3 loop
magnetometerSeedUsable := magnetometerSeedUsable
and abs(magnetometer.magneticFieldBodyFlu_T[axis]) < FiniteMagnitudeLimit
and abs(tuning.localMagneticFieldWorldEnu_T[axis]) < FiniteMagnitudeLimit
and magnetometer.covarianceBody_T2[axis, axis] > 0.0;
end for;
imuPayloadHeld := not imuPayloadFinite;
if imuPayloadFinite then
imuAngularVelocityHeldNext_rad_s := imu.angularVelocityBodyFlu_rad_s;
imuSpecificForceHeldNext_m_s2 := imu.specificForceBodyFlu_m_s2;
imuTimestampHeldNext_s := imu.timestamp_s;
else
imuAngularVelocityHeldNext_rad_s := imuAngularVelocityHeldPrevious_rad_s;
imuSpecificForceHeldNext_m_s2 := imuSpecificForceHeldPrevious_m_s2;
imuTimestampHeldNext_s := imuTimestampHeldPrevious_s;
end if;
mocapStaleNext_s := if mocapNew then 0.0
else mocapStalePrevious_s + dt;
gpsStaleNext_s := if gpsNew
and (gps.positionValid or gps.velocityValid) then 0.0
else gpsStalePrevious_s + dt;
opticalFlowStaleNext_s := if opticalFlowNew
then 0.0 else opticalFlowStalePrevious_s + dt;
// THE ANCHOR SOURCE: the most authoritative source still delivering.
// The existing correction priority already ranks the sources by
// authority -- mocap is external ground truth, GPS is an absolute
// position fix, optical flow is a relative velocity inference that
// cannot observe position at all -- so the same order names the source
// the filter is supposed to be anchored to.
anchorPresent := if mocapStaleNext_s <= tuning.aidingStaleTimeout_s then
SourceMocap
elseif gpsStaleNext_s <= tuning.aidingStaleTimeout_s then SourceGps
elseif opticalFlowStaleNext_s <= tuning.aidingStaleTimeout_s then
SourceOpticalFlow
else SourceNone;
anchorSourceNext := if anchorPresent <> SourceNone then anchorPresent
else anchorSourcePrevious;
aidingLive := anchorPresent <> SourceNone;
ladderConfigured := tuning.covarianceInflateWindow_s > 0.0
and tuning.covarianceInflateWindow_s < FiniteMagnitudeLimit
and tuning.covarianceInflateTimeConstant_s > 0.0
and tuning.covarianceInflateTimeConstant_s < FiniteMagnitudeLimit
and tuning.aidingStaleTimeout_s > 0.0
and tuning.aidingStaleTimeout_s < FiniteMagnitudeLimit
and tuning.aidingDivergentWindow_s > tuning.covarianceInflateWindow_s
and tuning.aidingDivergentWindow_s < FiniteMagnitudeLimit;
inflateCovarianceNow := ladderConfigured and initializedPrevious
and not reset
and rejectionElapsedPrevious_s >= tuning.covarianceInflateWindow_s;
aidingDivergent := ladderConfigured and initializedPrevious and not reset
and rejectionElapsedPrevious_s >= tuning.aidingDivergentWindow_s;
// STAGE 3 configuration admission. Re-seed is opt-in: a non-positive or
// non-finite window disables it, and an enabled window that does not
// exceed the divergent window disables it too. A misconfigured value
// therefore silences only the re-seed and never disturbs the two
// always-on stages, the same affirmative-admission discipline the rest of
// the ladder uses on its own windows.
reseedConfigured := ladderConfigured
and tuning.aidingReseedWindow_s > 0.0
and tuning.aidingReseedWindow_s < FiniteMagnitudeLimit
and tuning.aidingReseedWindow_s > tuning.aidingDivergentWindow_s;
// The re-seed fires only past the re-seed window, on the anchor source
// PRESENT this tick, and only when that source delivered a fresh sample
// this tick that passes the same finite-data seed contract the
// initialization branch enforces (mocap position and a normalizable
// attitude; GPS position with positionValid). A non-finite payload falls
// through exactly as an absent one, so a NaN can never seed the state.
reseedNow := reseedConfigured and initializedPrevious and not reset
and rejectionElapsedPrevious_s >= tuning.aidingReseedWindow_s
and ((anchorPresent == SourceMocap and mocapNew and mocapSeedUsable)
or (anchorPresent == SourceGps and gpsNew and gpsSeedUsable));
recoveryStage := if not ladderConfigured then RecoveryMisconfigured
elseif aidingDivergent then RecoveryAidingDivergent
elseif inflateCovarianceNow then RecoveryCovarianceInflated
else RecoveryNominal;
// Exclusivity keys to the source PRESENT this tick, not the latched one:
// while the anchor is between samples there is nothing for it to be
// exclusive against, and blocking the others would simply stop all
// correction during re-acquisition.
anchorExclusive := recoveryStage == RecoveryCovarianceInflated
and anchorPresent <> SourceNone;
alignmentWaitNext_s := if initializedPrevious and not reset then 0.0
elseif imuUsable then alignmentWaitPrevious_s + dt
else alignmentWaitPrevious_s;
alignmentTimedOut := tuning.initialAlignmentTimeout_s > 0.0
and tuning.initialAlignmentTimeout_s < FiniteMagnitudeLimit
and alignmentWaitNext_s >= tuning.initialAlignmentTimeout_s;
alignmentReady := mocapSeedUsable or not alignmentGateConfigured
or (imuUsable
and (quietElapsedNext_s >= tuning.initialAlignmentWindow_s
or alignmentTimedOut));
alignmentPending := (not initializedPrevious or reset)
and not alignmentReady;
if alignmentPending then
working := copyState(previous);
alignmentSource := AlignmentNone;
elseif not initializedPrevious or reset then
(working, pseudoPositionHoldNext_m, alignmentSource,
magnetometerCorrectionAccepted, magnetometerTimestampConsumedNext_s) :=
initializeTick(mocap, gps, magnetometer, tuning,
mocapSeedUsable, gpsSeedUsable, magnetometerSeedUsable, imuUsable,
alignmentGateConfigured, imuQuiet, alignmentTimedOut,
alignmentSpecificForce_m_s2, quietFilterGain, alignmentWaitNext_s,
dt, gravityWorldEnu_m_s2, imuTimestampHeldNext_s,
imuAngularVelocityHeldNext_rad_s, imuSpecificForceHeldNext_m_s2,
magnetometerTimestampConsumedPrevious_s);
// A seed is already an observation of the position. Consume that packet
// here so the same held noise is not fused again on the next IMU tick.
if mocapSeedUsable then
mocapTimestampConsumedNext_s := if abs(mocap.timestamp_s) < FiniteMagnitudeLimit
then mocap.timestamp_s else mocapTimestampConsumedPrevious_s;
elseif gpsSeedUsable then
gpsTimestampConsumedNext_s := if abs(gps.timestamp_s) < FiniteMagnitudeLimit
then gps.timestamp_s else gpsTimestampConsumedPrevious_s;
end if;
else
(
working, predictionAccepted, stationaryImuCorrectionAccepted,
mocapCorrectionAccepted, gpsPositionCorrectionAccepted, gpsVelocityCorrectionAccepted,
magnetometerCorrectionAccepted, barometerCorrectionAccepted, opticalFlowCorrectionAccepted,
correctionAttempted, correctionAccepted, correctionOutcome,
correctionSource, normalizedInnovationSquared, reseeded,
reseedSource, zeroVelocityCorrectionAccepted, pseudoPositionCorrectionAccepted,
pseudoPositionHoldNext_m, mocapTimestampConsumedNext_s, gpsTimestampConsumedNext_s,
magnetometerTimestampConsumedNext_s, barometerTimestampConsumedNext_s, opticalFlowTimestampConsumedNext_s
) := advanceStateTick(
previous, imu, mocap,
gps, magnetometer, barometer,
opticalFlow, tuning, gravityWorldEnu_m_s2,
dt, inflateCovarianceNow, imuUsable,
imuPayloadFinite, vehicleAtRest, reseedNow,
anchorExclusive, mocapNew, gpsNew,
magnetometerNew, barometerNew, opticalFlowNew,
aidingLive, ladderConfigured, imuQuiet,
anchorPresent, rejectionElapsedPrevious_s, imuTimestampHeldNext_s,
imuAngularVelocityHeldNext_rad_s, imuSpecificForceHeldNext_m_s2, pseudoPositionHoldPrevious_m,
mocapTimestampConsumedPrevious_s, gpsTimestampConsumedPrevious_s, magnetometerTimestampConsumedPrevious_s,
barometerTimestampConsumedPrevious_s, opticalFlowTimestampConsumedPrevious_s
);
if anchorPresent == SourceNone then
// Total aiding loss FREEZES the clock; it does not clear it.
// Clearing here was wrong in a way that reads as correct: losing
// every source is not evidence that a divergence has been resolved,
// it is a strictly worse situation, and de-escalating the report to
// nominal at that moment tells the wrapper the opposite of the
// truth. Measured: a divergent stage fell back to nominal 403 ms
// after total dropout, while completely unaided. Frozen, the report
// persists and resumes from where it stood if aiding returns.
consecutiveRejectionsNext := consecutiveRejectionsPrevious;
rejectionElapsedNext_s := rejectionElapsedPrevious_s;
elseif anchorSourceNext <> anchorSourcePrevious then
// A different live source has taken over. Its elapsed time describes
// the source that just lost the anchor, so it does not apply.
consecutiveRejectionsNext := 0;
rejectionElapsedNext_s := 0.0;
elseif correctionAttempted and correctionSource == anchorPresent then
if correctionAccepted then
consecutiveRejectionsNext := 0;
rejectionElapsedNext_s := 0.0;
else
consecutiveRejectionsNext := consecutiveRejectionsPrevious + 1;
rejectionElapsedNext_s := rejectionElapsedPrevious_s + dt;
end if;
else
// The anchor held but said nothing this tick, whether because it is
// between samples or because it has gone quiet. Either way the
// state has spent another tick unanchored, so the clock advances.
consecutiveRejectionsNext := consecutiveRejectionsPrevious;
rejectionElapsedNext_s := rejectionElapsedPrevious_s + dt;
end if;
mocapRejectionsNext := if correctionSource == SourceMocap then
(if correctionAccepted then 0 else mocapRejectionsPrevious + 1)
else mocapRejectionsPrevious;
gpsRejectionsNext := if correctionSource == SourceGps then
(if correctionAccepted then 0 else gpsRejectionsPrevious + 1)
else gpsRejectionsPrevious;
opticalFlowRejectionsNext :=
if not reseedNow and opticalFlowNew
and (not anchorExclusive or anchorPresent == SourceOpticalFlow) then
(if opticalFlowCorrectionAccepted then 0
else opticalFlowRejectionsPrevious + 1)
else opticalFlowRejectionsPrevious;
// STAGE 3 telemetry and clock reset. The re-seed is a deliberate
// replacement of the state, so the tick reports CorrectionReseeded
// rather than the gate acceptance the same-tick fusion recorded, and it
// clears the rejection clock and every per-source counter: the
// divergence they were measuring has been resolved by construction.
if reseeded then
correctionOutcome := CorrectionReseeded;
correctionSource := reseedSource;
consecutiveRejectionsNext := 0;
rejectionElapsedNext_s := 0.0;
mocapRejectionsNext := 0;
gpsRejectionsNext := 0;
opticalFlowRejectionsNext := 0;
end if;
end if;
positionNext := working.positionWorldEnu_m;
velocityNext := working.velocityWorldEnu_m_s;
quaternionNext := working.quaternionWorldBody;
gyroscopeBiasNext := working.gyroscopeBiasBodyFlu_rad_s;
accelerometerBiasNext := working.accelerometerBiasBodyFlu_m_s2;
covarianceNext := working.covariance;
covarianceRootNext := working.covarianceRoot;
barometerBiasCrossCovarianceNext := working.barometerBiasCrossCovariance;
barometerBiasNext_m := working.barometerBias_m;
barometerBiasVarianceNext_m2 := working.barometerBiasVariance_m2;
initializedNext := not alignmentPending;
estimateValid := initializedNext
and nominalStateFinite(positionNext, velocityNext, quaternionNext);
end step;