LCOV - code coverage report
Current view: top level - AP_NavEKF2 - AP_NavEKF2_PosVelFusion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 362 529 68.4 %
Date: 2026-09-09 11:02:08 Functions: 8 12 66.7 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : 
       3             : #include "AP_NavEKF2.h"
       4             : #include "AP_NavEKF2_core.h"
       5             : #include <AP_DAL/AP_DAL.h>
       6             : 
       7             : extern const AP_HAL::HAL& hal;
       8             : 
       9             : /********************************************************
      10             : *                   RESET FUNCTIONS                     *
      11             : ********************************************************/
      12             : 
      13             : // Reset XY velocity states to last GPS measurement if available or to zero if in constant position mode or if PV aiding is not absolute
      14             : // Do not reset vertical velocity using GPS as there is baro alt available to constrain drift
      15         860 : void NavEKF2_core::ResetVelocity(void)
      16             : {
      17             :     // reset the corresponding covariances
      18         860 :     zeroRows(P,3,4);
      19         860 :     zeroCols(P,3,4);
      20             :     
      21         860 :     if (PV_AidingMode != AID_ABSOLUTE) {
      22         562 :         stateStruct.velocity.xy().zero();
      23             :         // set the variances using the measurement noise parameter
      24         562 :         P[4][4] = P[3][3] = sq(frontend->_gpsHorizVelNoise);
      25             :     } else {
      26             :         // reset horizontal velocity states to the GPS velocity if available
      27         298 :         if (imuSampleTime_ms - lastTimeGpsReceived_ms < 250) {
      28             :             // correct for antenna position
      29         294 :             gps_elements gps_corrected = gpsDataNew;
      30         294 :             CorrectGPSForAntennaOffset(gps_corrected);
      31         294 :             stateStruct.velocity.x  = gps_corrected.vel.x;
      32         294 :             stateStruct.velocity.y  = gps_corrected.vel.y;
      33             :             // set the variances using the reported GPS speed accuracy
      34         294 :             P[4][4] = P[3][3] = sq(MAX(frontend->_gpsHorizVelNoise,gpsSpdAccuracy));
      35           4 :         } else if (imuSampleTime_ms - extNavVelMeasTime_ms < 250) {
      36             :             // use external nav data as the 2nd preference
      37           0 :             stateStruct.velocity = extNavVelDelayed.vel;
      38           0 :             P[5][5] = P[4][4] = P[3][3] = sq(extNavVelDelayed.err);
      39             :         } else {
      40           4 :             stateStruct.velocity.x  = 0.0f;
      41           4 :             stateStruct.velocity.y  = 0.0f;
      42             :             // set the variances using the likely speed range
      43           4 :             P[4][4] = P[3][3] = sq(25.0f);
      44             :         }
      45             :         // clear the timeout flags and counters
      46         298 :         velTimeout = false;
      47         298 :         lastVelPassTime_ms = imuSampleTime_ms;
      48             :     }
      49       13262 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
      50       12402 :         storedOutput[i].velocity.x = stateStruct.velocity.x;
      51       12402 :         storedOutput[i].velocity.y = stateStruct.velocity.y;
      52             :     }
      53         860 :     outputDataNew.velocity.x = stateStruct.velocity.x;
      54         860 :     outputDataNew.velocity.y = stateStruct.velocity.y;
      55         860 :     outputDataDelayed.velocity.x = stateStruct.velocity.x;
      56         860 :     outputDataDelayed.velocity.y = stateStruct.velocity.y;
      57             : 
      58             : 
      59             : 
      60         860 : }
      61             : 
      62             : // resets position states to last GPS measurement or to zero if in constant position mode
      63         860 : void NavEKF2_core::ResetPosition(void)
      64             : {
      65             :     // Store the position before the reset so that we can record the reset delta
      66         860 :     posResetNE.x = stateStruct.position.x;
      67         860 :     posResetNE.y = stateStruct.position.y;
      68             : 
      69             :     // reset the corresponding covariances
      70         860 :     zeroRows(P,6,7);
      71         860 :     zeroCols(P,6,7);
      72             :     
      73         860 :     if (PV_AidingMode != AID_ABSOLUTE) {
      74             :         // reset all position state history to the last known position
      75         562 :         stateStruct.position.x = lastKnownPositionNE.x;
      76         562 :         stateStruct.position.y = lastKnownPositionNE.y;
      77             :         // set the variances using the position measurement noise parameter
      78         562 :         P[6][6] = P[7][7] = sq(frontend->_gpsHorizPosNoise);
      79             :     } else  {
      80             :         // Use GPS data as first preference if fresh data is available
      81         298 :         if (imuSampleTime_ms - lastTimeGpsReceived_ms < 250) {
      82             :             // correct for antenna position
      83         294 :             gps_elements gps_corrected = gpsDataNew;
      84         294 :             CorrectGPSForAntennaOffset(gps_corrected);
      85             :             // record the ID of the GPS for the data we are using for the reset
      86         294 :             last_gps_idx = gps_corrected.sensor_idx;
      87             :             // write to state vector and compensate for offset  between last GPS measurement and the EKF time horizon
      88         294 :             stateStruct.position.x = gps_corrected.pos.x  + 0.001f*gps_corrected.vel.x*(float(imuDataDelayed.time_ms) - float(gps_corrected.time_ms));
      89         294 :             stateStruct.position.y = gps_corrected.pos.y  + 0.001f*gps_corrected.vel.y*(float(imuDataDelayed.time_ms) - float(gps_corrected.time_ms));
      90             :             // set the variances using the position measurement noise parameter
      91         294 :             P[6][6] = P[7][7] = sq(MAX(gpsPosAccuracy,frontend->_gpsHorizPosNoise));
      92             :             // clear the timeout flags and counters
      93         294 :             posTimeout = false;
      94         294 :             lastPosPassTime_ms = imuSampleTime_ms;
      95           4 :         } else if (imuSampleTime_ms - rngBcnLast3DmeasTime_ms < 250) {
      96             :             // use the range beacon data as a second preference
      97           0 :             stateStruct.position.x = receiverPos.x;
      98           0 :             stateStruct.position.y = receiverPos.y;
      99             :             // set the variances from the beacon alignment filter
     100           0 :             P[6][6] = receiverPosCov[0][0];
     101           0 :             P[7][7] = receiverPosCov[1][1];
     102             :             // clear the timeout flags and counters
     103           0 :             rngBcnTimeout = false;
     104           0 :             lastRngBcnPassTime_ms = imuSampleTime_ms;
     105           4 :         } else if (imuSampleTime_ms - extNavDataDelayed.time_ms < 250) {
     106             :             // use external nav data as the third preference
     107           0 :             ext_nav_elements extNavCorrected = extNavDataDelayed;
     108           0 :             CorrectExtNavForSensorOffset(extNavCorrected.pos);
     109           0 :             stateStruct.position.x = extNavCorrected.pos.x;
     110           0 :             stateStruct.position.y = extNavCorrected.pos.y;
     111             :             // set the variances from the external nav filter
     112           0 :             P[7][7] = P[6][6] = sq(extNavCorrected.posErr);
     113             :         }
     114             :     }
     115       13262 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
     116       12402 :         storedOutput[i].position.x = stateStruct.position.x;
     117       12402 :         storedOutput[i].position.y = stateStruct.position.y;
     118             :     }
     119         860 :     outputDataNew.position.x = stateStruct.position.x;
     120         860 :     outputDataNew.position.y = stateStruct.position.y;
     121         860 :     outputDataDelayed.position.x = stateStruct.position.x;
     122         860 :     outputDataDelayed.position.y = stateStruct.position.y;
     123             : 
     124             :     // Calculate the position jump due to the reset
     125         860 :     posResetNE.x = stateStruct.position.x - posResetNE.x;
     126         860 :     posResetNE.y = stateStruct.position.y - posResetNE.y;
     127             : 
     128         860 :     posNEResetCount++;
     129             : 
     130         860 : }
     131             : 
     132             : // reset the stateStruct's NE position to the specified position
     133             : //    posResetNE is updated to hold the change in position
     134             : //    storedOutput, outputDataNew and outputDataDelayed are updated with the change in position
     135             : //    posNEResetCount is incremented to record the reset
     136           0 : void NavEKF2_core::ResetPositionNE(ftype posN, ftype posE)
     137             : {
     138             :     // Store the position before the reset so that we can record the reset delta
     139           0 :     const Vector3F posOrig = stateStruct.position;
     140             : 
     141             :     // Set the position states to the new position
     142           0 :     stateStruct.position.x = posN;
     143           0 :     stateStruct.position.y = posE;
     144             : 
     145             :     // Calculate the position offset due to the reset
     146           0 :     posResetNE.x = stateStruct.position.x - posOrig.x;
     147           0 :     posResetNE.y = stateStruct.position.y - posOrig.y;
     148             : 
     149             :     // Add the offset to the output observer states
     150           0 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
     151           0 :         storedOutput[i].position.x += posResetNE.x;
     152           0 :         storedOutput[i].position.y += posResetNE.y;
     153             :     }
     154           0 :     outputDataNew.position.x += posResetNE.x;
     155           0 :     outputDataNew.position.y += posResetNE.y;
     156           0 :     outputDataDelayed.position.x += posResetNE.x;
     157           0 :     outputDataDelayed.position.y += posResetNE.y;
     158             : 
     159           0 :     posNEResetCount++;
     160           0 : }
     161             : 
     162             : // reset the vertical position state using the last height measurement
     163         566 : void NavEKF2_core::ResetHeight(void)
     164             : {
     165             :     // Store the position before the reset so that we can record the reset delta
     166         566 :     posResetD = stateStruct.position.z;
     167             : 
     168             :     // write to the state vector
     169         566 :     stateStruct.position.z = -hgtMea;
     170         566 :     outputDataNew.position.z = stateStruct.position.z;
     171         566 :     outputDataDelayed.position.z = stateStruct.position.z;
     172             : 
     173             :     // reset the terrain state height
     174         566 :     if (onGround) {
     175             :         // assume vehicle is sitting on the ground
     176         560 :         terrainState = stateStruct.position.z + rngOnGnd;
     177             :     } else {
     178             :         // can make no assumption other than vehicle is not below ground level
     179           6 :         terrainState = MAX(stateStruct.position.z + rngOnGnd , terrainState);
     180             :     }
     181        8626 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
     182        8060 :         storedOutput[i].position.z = stateStruct.position.z;
     183             :     }
     184         566 :     vertCompFiltState.pos = stateStruct.position.z;
     185             : 
     186             :     // Calculate the position jump due to the reset
     187         566 :     posResetD = stateStruct.position.z - posResetD;
     188             : 
     189         566 :     posDResetCount++;
     190             : 
     191             :     // clear the timeout flags and counters
     192         566 :     hgtTimeout = false;
     193         566 :     lastHgtPassTime_ms = imuSampleTime_ms;
     194             : 
     195             :     // reset the corresponding covariances
     196         566 :     zeroRows(P,8,8);
     197         566 :     zeroCols(P,8,8);
     198             : 
     199             :     // set the variances to the measurement variance
     200         566 :     P[8][8] = posDownObsNoise;
     201             : 
     202             :     // Reset the vertical velocity state using GPS vertical velocity if we are airborne
     203             :     // Check that GPS vertical velocity data is available and can be used
     204         566 :     if (inFlight && !gpsNotAvailable && frontend->_fusionModeGPS == 0 &&
     205           6 :         dal.gps().have_vertical_velocity()) {
     206           6 :         stateStruct.velocity.z =  gpsDataNew.vel.z;
     207         560 :     } else if (inFlight && useExtNavVel) {
     208           0 :         stateStruct.velocity.z = extNavVelNew.vel.z;
     209         560 :     } else if (onGround) {
     210         560 :         stateStruct.velocity.z = 0.0f;
     211             :     }
     212        8626 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
     213        8060 :         storedOutput[i].velocity.z = stateStruct.velocity.z;
     214             :     }
     215         566 :     outputDataNew.velocity.z = stateStruct.velocity.z;
     216         566 :     outputDataDelayed.velocity.z = stateStruct.velocity.z;
     217         566 :     vertCompFiltState.vel = outputDataNew.velocity.z;
     218             : 
     219             :     // reset the corresponding covariances
     220         566 :     zeroRows(P,5,5);
     221         566 :     zeroCols(P,5,5);
     222             : 
     223             :     // set the variances to the measurement variance
     224         566 :     if (useExtNavVel) {
     225           0 :         P[5][5] = sq(extNavVelNew.err);
     226             :     } else {
     227         566 :         P[5][5] = sq(frontend->_gpsVertVelNoise);
     228             :     }
     229         566 : }
     230             : 
     231             : // reset the stateStruct's D position
     232             : //    posResetD is updated to hold the change in position
     233             : //    storedOutput, outputDataNew and outputDataDelayed are updated with the change in position
     234             : //    posDResetCount is incremented to record the reset
     235           0 : void NavEKF2_core::ResetPositionD(ftype posD)
     236             : {
     237             :     // Store the position before the reset so that we can record the reset delta
     238           0 :     const ftype posDOrig = stateStruct.position.z;
     239             : 
     240             :     // write to the state vector
     241           0 :     stateStruct.position.z = posD;
     242             : 
     243             :     // Calculate the position jump due to the reset
     244           0 :     posResetD = stateStruct.position.z - posDOrig;
     245             : 
     246             :     // Add the offset to the output observer states
     247           0 :     outputDataNew.position.z += posResetD;
     248           0 :     vertCompFiltState.pos = outputDataNew.position.z;
     249           0 :     outputDataDelayed.position.z += posResetD;
     250           0 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
     251           0 :         storedOutput[i].position.z += posResetD;
     252             :     }
     253             : 
     254           0 :     posDResetCount++;
     255           0 : }
     256             : 
     257             : // Zero the EKF height datum
     258             : // Return true if the height datum reset has been performed
     259        2992 : bool NavEKF2_core::resetHeightDatum(void)
     260             : {
     261        2992 :     if (activeHgtSource == HGT_SOURCE_RNG || !onGround) {
     262             :         // only allow resets when on the ground.
     263             :         // If using using rangefinder for height then never perform a
     264             :         // reset of the height datum
     265             :         return false;
     266             :     }
     267             :     // record the old height estimate
     268        2986 :     ftype oldHgt = -stateStruct.position.z;
     269             :     // reset the barometer so that it reads zero at the current height
     270        2986 :     dal.baro().update_calibration();
     271             :     // reset the height state
     272        2986 :     stateStruct.position.z = 0.0f;
     273             :     // adjust the height of the EKF origin so that the origin plus baro height before and after the reset is the same
     274             : 
     275        2986 :     if (validOrigin) {
     276        2126 :         if (!gpsGoodToAlign) {
     277             :             // if we don't have GPS lock then we shouldn't be doing a
     278             :             // resetHeightDatum, but if we do then the best option is
     279             :             // to maintain the old error
     280          36 :             EKF_origin.alt += (int32_t)(100.0f * oldHgt);
     281             :         } else {
     282             :             // if we have a good GPS lock then reset to the GPS
     283             :             // altitude. This ensures the reported AMSL alt from
     284             :             // getLLH() is equal to GPS altitude, while also ensuring
     285             :             // that the relative alt is zero
     286        2090 :             EKF_origin.copy_alt_from(dal.gps().location());
     287             :         }
     288        2126 :         ekfGpsRefHgt = (double)0.01 * (double)EKF_origin.alt;
     289             :     }
     290             : 
     291             :     // set the terrain state to zero (on ground). The adjustment for
     292             :     // frame height will get added in the later constraints
     293        2986 :     terrainState = 0;
     294        2986 :     return true;
     295             : }
     296             : 
     297             : /*
     298             :   correct GPS data for position offset of antenna phase centre relative to the IMU
     299             : */
     300      255468 : void NavEKF2_core::CorrectGPSForAntennaOffset(gps_elements &gps_data) const
     301             : {
     302      255468 :     const Vector3F posOffsetBody = dal.gps().get_antenna_offset(gpsDataDelayed.sensor_idx).toftype() - accelPosOffset;
     303      255468 :     if (posOffsetBody.is_zero()) {
     304      254258 :         return;
     305             :     }
     306             : 
     307             :     // Don't fuse velocity data if GPS doesn't support it
     308        1210 :     if (fuseVelData) {
     309             :         // TODO use a filtered angular rate with a group delay that matches the GPS delay
     310        1206 :         Vector3F angRate = imuDataDelayed.delAng * (1.0f/imuDataDelayed.delAngDT);
     311        1206 :         Vector3F velOffsetBody = angRate % posOffsetBody;
     312        1206 :         Vector3F velOffsetEarth = prevTnb.mul_transpose(velOffsetBody);
     313        1206 :         gps_data.vel.x -= velOffsetEarth.x;
     314        1206 :         gps_data.vel.y -= velOffsetEarth.y;
     315        1206 :         gps_data.vel.z -= velOffsetEarth.z;
     316             :     }
     317             : 
     318        1210 :     Vector3F posOffsetEarth = prevTnb.mul_transpose(posOffsetBody);
     319        1210 :     gps_data.pos.x -= posOffsetEarth.x;
     320        1210 :     gps_data.pos.y -= posOffsetEarth.y;
     321        1210 :     gps_data.hgt += posOffsetEarth.z;
     322             : }
     323             : 
     324             : // correct external navigation earth-frame position using sensor body-frame offset
     325           0 : void NavEKF2_core::CorrectExtNavForSensorOffset(Vector3F &ext_position) const
     326             : {
     327             : #if HAL_VISUALODOM_ENABLED
     328           0 :     const auto *visual_odom = dal.visualodom();
     329           0 :     if (visual_odom == nullptr) {
     330           0 :         return;
     331             :     }
     332           0 :     const Vector3F posOffsetBody = visual_odom->get_pos_offset().toftype() - accelPosOffset;
     333           0 :     if (posOffsetBody.is_zero()) {
     334             :         return;
     335             :     }
     336           0 :     Vector3F posOffsetEarth = prevTnb.mul_transpose(posOffsetBody);
     337           0 :     ext_position.x -= posOffsetEarth.x;
     338           0 :     ext_position.y -= posOffsetEarth.y;
     339           0 :     ext_position.z -= posOffsetEarth.z;
     340             : #endif
     341             : }
     342             : 
     343             : // correct external navigation earth-frame velocity using sensor body-frame offset
     344           0 : void NavEKF2_core::CorrectExtNavVelForSensorOffset(Vector3F &ext_velocity) const
     345             : {
     346             : #if HAL_VISUALODOM_ENABLED
     347           0 :     const auto *visual_odom = dal.visualodom();
     348           0 :     if (visual_odom == nullptr) {
     349           0 :         return;
     350             :     }
     351           0 :     const Vector3F posOffsetBody = visual_odom->get_pos_offset().toftype() - accelPosOffset;
     352           0 :     if (posOffsetBody.is_zero()) {
     353             :         return;
     354             :     }
     355             :     // TODO use a filtered angular rate with a group delay that matches the sensor delay
     356           0 :     const Vector3F angRate = imuDataDelayed.delAng * (1.0f/imuDataDelayed.delAngDT);
     357           0 :     ext_velocity += get_vel_correction_for_sensor_offset(posOffsetBody, prevTnb, angRate);
     358             : #endif
     359             : }
     360             : 
     361             : /********************************************************
     362             : *                   FUSE MEASURED_DATA                  *
     363             : ********************************************************/
     364             : // select fusion of velocity, position and height measurements
     365     3248540 : void NavEKF2_core::SelectVelPosFusion()
     366             : {
     367             :     // Check if the magnetometer has been fused on that time step and the filter is running at faster than 200 Hz
     368             :     // If so, don't fuse measurements on this time step to reduce frame over-runs
     369             :     // Only allow one time slip to prevent high rate magnetometer data preventing fusion of other measurements
     370     3248540 :     if (magFusePerformed && dtIMUavg < 0.005f && !posVelFusionDelayed) {
     371       18168 :         posVelFusionDelayed = true;
     372       18168 :         return;
     373             :     } else {
     374     3230372 :         posVelFusionDelayed = false;
     375             :     }
     376             : 
     377             :     // Check for data at the fusion time horizon
     378     3230372 :     extNavDataToFuse = storedExtNav.recall(extNavDataDelayed, imuDataDelayed.time_ms);
     379     3230372 :     extNavVelToFuse = storedExtNavVel.recall(extNavVelDelayed, imuDataDelayed.time_ms);
     380     3230372 :     if (extNavVelToFuse) {
     381           0 :         CorrectExtNavVelForSensorOffset(extNavVelDelayed.vel);
     382             :     }
     383             : 
     384             :     // read GPS data from the sensor and check for new data in the buffer
     385     3230372 :     readGpsData();
     386     3230372 :     gpsDataToFuse = storedGPS.recall(gpsDataDelayed,imuDataDelayed.time_ms);
     387             : 
     388             :     // Determine if we need to fuse position and velocity data on this time step
     389     3230372 :     if (gpsDataToFuse && PV_AidingMode == AID_ABSOLUTE) {
     390             :         // set fusion request flags
     391      254880 :         if (frontend->_fusionModeGPS <= 1) {
     392      254880 :             fuseVelData = true;
     393             :         } else {
     394           0 :             fuseVelData = false;
     395             :         }
     396      254880 :         fusePosData = true;
     397      254880 :         extNavUsedForPos = false;
     398             : 
     399             :         // correct for antenna position
     400      254880 :         CorrectGPSForAntennaOffset(gpsDataDelayed);
     401             : 
     402             :         // copy corrected GPS data to observation vector
     403      254880 :         if (fuseVelData) {
     404      254880 :             velPosObs[0] = gpsDataDelayed.vel.x;
     405      254880 :             velPosObs[1] = gpsDataDelayed.vel.y;
     406      254880 :             velPosObs[2] = gpsDataDelayed.vel.z;
     407             :         }
     408      254880 :         velPosObs[3] = gpsDataDelayed.pos.x;
     409      254880 :         velPosObs[4] = gpsDataDelayed.pos.y;
     410             : 
     411     2975492 :     } else if (extNavDataToFuse && PV_AidingMode == AID_ABSOLUTE) {
     412             :         // This is a special case that uses and external nav system for position
     413           0 :         extNavUsedForPos = true;
     414           0 :         activeHgtSource = HGT_SOURCE_EXTNAV;
     415           0 :         fuseVelData = false;
     416           0 :         fuseHgtData = true;
     417           0 :         fusePosData = true;
     418             : 
     419             :         // correct for external navigation sensor position
     420           0 :         CorrectExtNavForSensorOffset(extNavDataDelayed.pos);
     421             : 
     422           0 :         velPosObs[3] = extNavDataDelayed.pos.x;
     423           0 :         velPosObs[4] = extNavDataDelayed.pos.y;
     424           0 :         velPosObs[5] = extNavDataDelayed.pos.z;
     425             : 
     426             :         // if compass is disabled, also use it for yaw
     427           0 :         if (!use_compass()) {
     428           0 :             extNavUsedForYaw = true;
     429           0 :             if (!yawAlignComplete) {
     430           0 :                 extNavYawResetRequest = true;
     431           0 :                 magYawResetRequest = false;
     432           0 :                 gpsYawResetRequest = false;
     433           0 :                 controlMagYawReset();
     434           0 :                 finalInflightYawInit = true;
     435             :             } else {
     436           0 :                 fuseEulerYaw();
     437             :             }
     438             :         } else {
     439           0 :             extNavUsedForYaw = false;
     440             :         }
     441             : 
     442             :     } else {
     443     2975492 :         fuseVelData = false;
     444     2975492 :         fusePosData = false;
     445             :     }
     446             : 
     447     3230372 :     if (extNavVelToFuse && (frontend->_fusionModeGPS == 3)) {
     448           0 :         fuseVelData = true;
     449           0 :         velPosObs[0] = extNavVelDelayed.vel.x;
     450           0 :         velPosObs[1] = extNavVelDelayed.vel.y;
     451           0 :         velPosObs[2] = extNavVelDelayed.vel.z;
     452             :     }
     453             : 
     454             :     // we have GPS data to fuse and a request to align the yaw using the GPS course
     455     3230372 :     if (gpsYawResetRequest) {
     456     2080921 :         realignYawGPS();
     457             :     }
     458             : 
     459             :     // Select height data to be fused from the available baro, range finder and GPS sources
     460             : 
     461     3230372 :     selectHeightForFusion();
     462             : 
     463             :     // if we are using GPS, check for a change in receiver and reset position and height
     464     3230372 :     if (gpsDataToFuse && PV_AidingMode == AID_ABSOLUTE && gpsDataDelayed.sensor_idx != last_gps_idx) {
     465             :         // record the ID of the GPS that we are using for the reset
     466           0 :         last_gps_idx = gpsDataDelayed.sensor_idx;
     467             : 
     468             :         // Store the position before the reset so that we can record the reset delta
     469           0 :         posResetNE.x = stateStruct.position.x;
     470           0 :         posResetNE.y = stateStruct.position.y;
     471             : 
     472             :         // Set the position states to the position from the new GPS
     473           0 :         stateStruct.position.x = gpsDataDelayed.pos.x;
     474           0 :         stateStruct.position.y = gpsDataDelayed.pos.y;
     475             : 
     476             :         // Calculate the position offset due to the reset
     477           0 :         posResetNE.x = stateStruct.position.x - posResetNE.x;
     478           0 :         posResetNE.y = stateStruct.position.y - posResetNE.y;
     479             : 
     480             :         // Add the offset to the output observer states
     481           0 :         for (uint8_t i=0; i<imu_buffer_length; i++) {
     482           0 :             storedOutput[i].position.x += posResetNE.x;
     483           0 :             storedOutput[i].position.y += posResetNE.y;
     484             :         }
     485           0 :         outputDataNew.position.x += posResetNE.x;
     486           0 :         outputDataNew.position.y += posResetNE.y;
     487           0 :         outputDataDelayed.position.x += posResetNE.x;
     488           0 :         outputDataDelayed.position.y += posResetNE.y;
     489             : 
     490           0 :         posNEResetCount++;
     491             : 
     492             :         // If we are also using GPS as the height reference, reset the height
     493           0 :         if (activeHgtSource == HGT_SOURCE_GPS) {
     494             :             // Store the position before the reset so that we can record the reset delta
     495           0 :             posResetD = stateStruct.position.z;
     496             : 
     497             :             // write to the state vector
     498           0 :             stateStruct.position.z = -hgtMea;
     499             : 
     500             :             // Calculate the position jump due to the reset
     501           0 :             posResetD = stateStruct.position.z - posResetD;
     502             : 
     503             :             // Add the offset to the output observer states
     504           0 :             outputDataNew.position.z += posResetD;
     505           0 :             vertCompFiltState.pos = outputDataNew.position.z;
     506           0 :             outputDataDelayed.position.z += posResetD;
     507           0 :             for (uint8_t i=0; i<imu_buffer_length; i++) {
     508           0 :                 storedOutput[i].position.z += posResetD;
     509             :             }
     510             : 
     511           0 :             posDResetCount++;
     512             :         }
     513             :     }
     514             : 
     515             :     // check for external nav position reset
     516     3230372 :     if (extNavDataToFuse && (PV_AidingMode == AID_ABSOLUTE) && (frontend->_fusionModeGPS == 3) && extNavDataDelayed.posReset) {
     517           0 :         ResetPositionNE(extNavDataDelayed.pos.x, extNavDataDelayed.pos.y);
     518           0 :         if (activeHgtSource == HGT_SOURCE_EXTNAV) {
     519           0 :             ResetPositionD(-hgtMea);
     520             :         }
     521             :     }
     522             : 
     523             :     // If we are operating without any aiding, fuse in the last known position
     524             :     // to constrain tilt drift. This assumes a non-manoeuvring vehicle
     525             :     // Do this to coincide with the height fusion
     526     3230372 :     if (fuseHgtData && PV_AidingMode == AID_NONE) {
     527      106637 :         velPosObs[3] = lastKnownPositionNE.x;
     528      106637 :         velPosObs[4] = lastKnownPositionNE.y;
     529      106637 :         fusePosData = true;
     530      106637 :         fuseVelData = false;
     531             :     }
     532             : 
     533             :     // perform fusion
     534     3230372 :     if (fuseVelData || fusePosData || fuseHgtData) {
     535      861141 :         FuseVelPosNED();
     536             :         // clear the flags to prevent repeated fusion of the same data
     537      861141 :         fuseVelData = false;
     538      861141 :         fuseHgtData = false;
     539      861141 :         fusePosData = false;
     540             :     }
     541             : }
     542             : 
     543             : // fuse selected position, velocity and height measurements
     544      861141 : void NavEKF2_core::FuseVelPosNED()
     545             : {
     546             :     // health is set bad until test passed
     547             :     bool velHealth = false;                 // boolean true if velocity measurements have passed innovation consistency check
     548             :     bool posHealth = false;                 // boolean true if position measurements have passed innovation consistency check
     549             :     bool hgtHealth = false;                 // boolean true if height measurements have passed innovation consistency check
     550             : 
     551             :     // declare variables used to check measurement errors
     552      861141 :     Vector3F velInnov;
     553             : 
     554             :     // declare variables used to control access to arrays
     555      861141 :     bool fuseData[6] = {false,false,false,false,false,false};
     556             :     uint8_t stateIndex;
     557             :     uint8_t obsIndex;
     558             : 
     559             :     // declare variables used by state and covariance update calculations
     560             :     Vector6 R_OBS; // Measurement variances used for fusion
     561             :     Vector6 R_OBS_DATA_CHECKS; // Measurement variances used for data checks only
     562             :     ftype SK;
     563             : 
     564             :     // perform sequential fusion of GPS measurements. This assumes that the
     565             :     // errors in the different velocity and position components are
     566             :     // uncorrelated which is not true, however in the absence of covariance
     567             :     // data from the GPS receiver it is the only assumption we can make
     568             :     // so we might as well take advantage of the computational efficiencies
     569             :     // associated with sequential fusion
     570      861141 :     if (fuseVelData || fusePosData || fuseHgtData) {
     571             : 
     572             :         // calculate additional error in GPS position caused by manoeuvring
     573      861141 :         ftype posErr = frontend->gpsPosVarAccScale * accNavMag;
     574             : 
     575             :         // estimate the GPS Velocity, GPS horiz position and height measurement variances.
     576             :         // Use different errors if operating without external aiding using an assumed position or velocity of zero
     577      861141 :         if (PV_AidingMode == AID_NONE) {
     578      106637 :             if (tiltAlignComplete && motorsArmed) {
     579             :             // This is a compromise between corrections for gyro errors and reducing effect of manoeuvre accelerations on tilt estimate
     580       33425 :                 R_OBS[0] = sq(constrain_ftype(frontend->_noaidHorizNoise, 0.5f, 50.0f));
     581             :             } else {
     582             :                 // Use a smaller value to give faster initial alignment
     583       73212 :                 R_OBS[0] = sq(0.5f);
     584             :             }
     585      106637 :             R_OBS[1] = R_OBS[0];
     586      106637 :             R_OBS[2] = R_OBS[0];
     587      106637 :             R_OBS[3] = R_OBS[0];
     588      106637 :             R_OBS[4] = R_OBS[0];
     589      426548 :             for (uint8_t i=0; i<=2; i++) R_OBS_DATA_CHECKS[i] = R_OBS[i];
     590             :         } else {
     591      754504 :             if (gpsSpdAccuracy > 0.0f) {
     592             :                 // use GPS receivers reported speed accuracy if available and floor at value set by GPS velocity noise parameter
     593      754504 :                 R_OBS[0] = sq(constrain_ftype(gpsSpdAccuracy, frontend->_gpsHorizVelNoise, 50.0f));
     594      754504 :                 R_OBS[2] = sq(constrain_ftype(gpsSpdAccuracy, frontend->_gpsVertVelNoise, 50.0f));
     595           0 :             } else if (extNavVelToFuse) {
     596           0 :                 R_OBS[2] = R_OBS[0] = sq(constrain_ftype(extNavVelDelayed.err, 0.05f, 5.0f));
     597             :             } else {
     598             :                 // calculate additional error in GPS velocity caused by manoeuvring
     599           0 :                 R_OBS[0] = sq(constrain_ftype(frontend->_gpsHorizVelNoise, 0.05f, 5.0f)) + sq(frontend->gpsNEVelVarAccScale * accNavMag);
     600           0 :                 R_OBS[2] = sq(constrain_ftype(frontend->_gpsVertVelNoise,  0.05f, 5.0f)) + sq(frontend->gpsDVelVarAccScale  * accNavMag);
     601             :             }
     602      754504 :             R_OBS[1] = R_OBS[0];
     603             :             // Use GPS reported position accuracy if available and floor at value set by GPS position noise parameter
     604      754504 :             if (gpsPosAccuracy > 0.0f) {
     605      754504 :                 R_OBS[3] = sq(constrain_ftype(gpsPosAccuracy, frontend->_gpsHorizPosNoise, 100.0f));
     606           0 :             } else if (extNavUsedForPos) {
     607           0 :                 R_OBS[3] = sq(constrain_ftype(extNavDataDelayed.posErr, 0.01f, 10.0f));
     608             :             } else {
     609           0 :                 R_OBS[3] = sq(constrain_ftype(frontend->_gpsHorizPosNoise, 0.1f, 10.0f)) + sq(posErr);
     610             :             }
     611      754504 :             R_OBS[4] = R_OBS[3];
     612             :             // For data integrity checks we use the same measurement variances as used to calculate the Kalman gains for all measurements except GPS horizontal velocity
     613             :             // For horizontal GPS velocity we don't want the acceptance radius to increase with reported GPS accuracy so we use a value based on best GPS perfomrance
     614             :             // plus a margin for manoeuvres. It is better to reject GPS horizontal velocity errors early
     615             :             ftype obs_data_chk;
     616      754504 :             if (extNavVelToFuse) {
     617           0 :                 obs_data_chk = sq(constrain_ftype(extNavVelDelayed.err, 0.05f, 5.0f)) + sq(frontend->extNavVelVarAccScale * accNavMag);
     618             :             } else {
     619      754504 :                 obs_data_chk = sq(constrain_ftype(frontend->_gpsHorizVelNoise, 0.05f, 5.0f)) + sq(frontend->gpsNEVelVarAccScale * accNavMag);
     620             :             }
     621      754504 :             R_OBS_DATA_CHECKS[0] = R_OBS_DATA_CHECKS[1] = R_OBS_DATA_CHECKS[2] = obs_data_chk;
     622             :         }
     623      861141 :         R_OBS[5] = posDownObsNoise;
     624     3444564 :         for (uint8_t i=3; i<=5; i++) R_OBS_DATA_CHECKS[i] = R_OBS[i];
     625             : 
     626             :         // if vertical GPS velocity data and an independent height source is being used, check to see if the GPS vertical velocity and altimeter
     627             :         // innovations have the same sign and are outside limits. If so, then it is likely aliasing is affecting
     628             :         // the accelerometers and we should disable the GPS and barometer innovation consistency checks.
     629      861141 :         if (useGpsVertVel && fuseVelData && (frontend->_altSource != 2)) {
     630             :             // calculate innovations for height and vertical GPS vel measurements
     631      254880 :             ftype hgtErr  = stateStruct.position.z - velPosObs[5];
     632      254880 :             ftype velDErr = stateStruct.velocity.z - velPosObs[2];
     633             :             // check if they are the same sign and both more than 3-sigma out of bounds
     634      254880 :             if ((hgtErr*velDErr > 0.0f) && (sq(hgtErr) > 9.0f * (P[8][8] + R_OBS_DATA_CHECKS[5])) && (sq(velDErr) > 9.0f * (P[5][5] + R_OBS_DATA_CHECKS[2]))) {
     635         741 :                 badIMUdata = true;
     636             :             } else {
     637      254139 :                 badIMUdata = false;
     638             :             }
     639             :         }
     640             : 
     641             :         // calculate innovations and check GPS data validity using an innovation consistency check
     642             :         // test position measurements
     643      861141 :         if (fusePosData) {
     644             :             // test horizontal position measurements
     645      361517 :             innovVelPos[3] = stateStruct.position.x - velPosObs[3];
     646      361517 :             innovVelPos[4] = stateStruct.position.y - velPosObs[4];
     647      361517 :             varInnovVelPos[3] = P[6][6] + R_OBS_DATA_CHECKS[3];
     648      361517 :             varInnovVelPos[4] = P[7][7] + R_OBS_DATA_CHECKS[4];
     649             :             // apply an innovation consistency threshold test, but don't fail if bad IMU data
     650      361517 :             ftype maxPosInnov2 = sq(MAX(0.01f * (ftype)frontend->_gpsPosInnovGate, 1.0f))*(varInnovVelPos[3] + varInnovVelPos[4]);
     651      361517 :             posTestRatio = (sq(innovVelPos[3]) + sq(innovVelPos[4])) / maxPosInnov2;
     652      361517 :             posHealth = ((posTestRatio < 1.0f) || badIMUdata);
     653             :             // use position data if healthy or timed out
     654      361517 :             if (PV_AidingMode == AID_NONE) {
     655             :                 posHealth = true;
     656      106637 :                 lastPosPassTime_ms = imuSampleTime_ms;
     657      254880 :             } else if (posHealth || posTimeout) {
     658             :                 posHealth = true;
     659      253696 :                 lastPosPassTime_ms = imuSampleTime_ms;
     660             :                 // if timed out or outside the specified uncertainty radius, reset to the GPS
     661      253696 :                 if (posTimeout || ((P[6][6] + P[7][7]) > sq(float(frontend->_gpsGlitchRadiusMax)))) {
     662             :                     // reset the position to the current GPS position
     663          36 :                     ResetPosition();
     664             :                     // reset the velocity to the GPS velocity
     665          36 :                     ResetVelocity();
     666             :                     // don't fuse GPS data on this time step
     667          36 :                     fusePosData = false;
     668          36 :                     fuseVelData = false;
     669             :                     // Reset the position variances and corresponding covariances to a value that will pass the checks
     670          36 :                     zeroRows(P,6,7);
     671          36 :                     zeroCols(P,6,7);
     672          36 :                     P[6][6] = sq(float(0.5f*frontend->_gpsGlitchRadiusMax));
     673          36 :                     P[7][7] = P[6][6];
     674             :                     // Reset the normalised innovation to avoid failing the bad fusion tests
     675          36 :                     posTestRatio = 0.0f;
     676          36 :                     velTestRatio = 0.0f;
     677             :                 }
     678             :             }
     679             :         }
     680             : 
     681             :         // test velocity measurements
     682      861141 :         if (fuseVelData) {
     683             :             // test velocity measurements
     684             :             uint8_t imax = 2;
     685             :             // Don't fuse vertical velocity observations if inhibited by the user or if we are using synthetic data
     686      254844 :             if (!useExtNavVel && (frontend->_fusionModeGPS > 0 || PV_AidingMode != AID_ABSOLUTE ||
     687      254844 :                                   !dal.gps().have_vertical_velocity())) {
     688             :                 imax = 1;
     689             :             }
     690             :             ftype innovVelSumSq = 0; // sum of squares of velocity innovations
     691             :             ftype varVelSum = 0; // sum of velocity innovation variances
     692     1019376 :             for (uint8_t i = 0; i<=imax; i++) {
     693             :                 // velocity states start at index 3
     694      764532 :                 stateIndex   = i + 3;
     695             :                 // calculate innovations using blended and single IMU predicted states
     696      764532 :                 velInnov[i]  = stateStruct.velocity[i] - velPosObs[i]; // blended
     697             :                 // calculate innovation variance
     698      764532 :                 varInnovVelPos[i] = P[stateIndex][stateIndex] + R_OBS_DATA_CHECKS[i];
     699             :                 // sum the innovation and innovation variances
     700      764532 :                 innovVelSumSq += sq(velInnov[i]);
     701      764532 :                 varVelSum += varInnovVelPos[i];
     702             :             }
     703             :             // apply an innovation consistency threshold test, but don't fail if bad IMU data
     704             :             // calculate the test ratio
     705      254844 :             velTestRatio = innovVelSumSq / (varVelSum * sq(MAX(0.01f * (ftype)frontend->_gpsVelInnovGate, 1.0f)));
     706             :             // fail if the ratio is greater than 1
     707      254844 :             velHealth = ((velTestRatio < 1.0f)  || badIMUdata);
     708             :             // use velocity data if healthy, timed out, or in constant position mode
     709        1493 :             if (velHealth || velTimeout) {
     710             :                 velHealth = true;
     711             :                 // restart the timeout count
     712      253351 :                 lastVelPassTime_ms = imuSampleTime_ms;
     713             :                 // If we are doing full aiding and velocity fusion times out, reset to the GPS velocity
     714      253351 :                 if (PV_AidingMode == AID_ABSOLUTE && velTimeout) {
     715             :                     // reset the velocity to the GPS velocity
     716           0 :                     ResetVelocity();
     717             :                     // don't fuse GPS velocity data on this time step
     718           0 :                     fuseVelData = false;
     719             :                     // Reset the normalised innovation to avoid failing the bad fusion tests
     720           0 :                     velTestRatio = 0.0f;
     721             :                 }
     722             :             }
     723             :         }
     724             : 
     725             :         // test height measurements
     726      861141 :         if (fuseHgtData) {
     727             :             // calculate height innovations
     728      614058 :             innovVelPos[5] = stateStruct.position.z - velPosObs[5];
     729      614058 :             varInnovVelPos[5] = P[8][8] + R_OBS_DATA_CHECKS[5];
     730             :             // calculate the innovation consistency test ratio
     731      614058 :             hgtTestRatio = sq(innovVelPos[5]) / (sq(MAX(0.01f * (ftype)frontend->_hgtInnovGate, 1.0f)) * varInnovVelPos[5]);
     732             : 
     733             :             // when on ground we accept a larger test ratio to allow
     734             :             // the filter to handle large switch on IMU bias errors
     735             :             // without rejecting the height sensor
     736      614058 :             const ftype maxTestRatio = (PV_AidingMode == AID_NONE && onGround)? 3.0 : 1.0;
     737             : 
     738             :             // fail if the ratio is > maxTestRatio, but don't fail if bad IMU data
     739      614058 :             hgtHealth = (hgtTestRatio < maxTestRatio) || badIMUdata;
     740             : 
     741             :             // Fuse height data if healthy or timed out or in constant position mode
     742         574 :             if (hgtHealth || hgtTimeout) {
     743             :                 // Calculate a filtered value to be used by pre-flight health checks
     744             :                 // We need to filter because wind gusts can generate significant baro noise and we want to be able to detect bias errors in the inertial solution
     745      613490 :                 if (onGround) {
     746      163622 :                     ftype dtBaro = (imuSampleTime_ms - lastHgtPassTime_ms)*1.0e-3f;
     747             :                     const ftype hgtInnovFiltTC = 2.0f;
     748      163622 :                     ftype alpha = constrain_ftype(dtBaro/(dtBaro+hgtInnovFiltTC),0.0f,1.0f);
     749      163622 :                     hgtInnovFiltState += (innovVelPos[5]-hgtInnovFiltState)*alpha;
     750             :                 } else {
     751      449868 :                     hgtInnovFiltState = 0.0f;
     752             :                 }
     753             : 
     754             :                 // if timed out, reset the height
     755      613490 :                 if (hgtTimeout) {
     756           8 :                     ResetHeight();
     757             :                 }
     758             : 
     759             :                 // If we have got this far then declare the height data as healthy and reset the timeout counter
     760             :                 hgtHealth = true;
     761      613490 :                 lastHgtPassTime_ms = imuSampleTime_ms;
     762             :             }
     763             :         }
     764             : 
     765             :         // set range for sequential fusion of velocity and position measurements depending on which data is available and its health
     766      861141 :         if (fuseVelData && velHealth) {
     767      253351 :             fuseData[0] = true;
     768      253351 :             fuseData[1] = true;
     769      253351 :             if (useGpsVertVel || useExtNavVel) {
     770      253351 :                 fuseData[2] = true;
     771             :             }
     772      253351 :             tiltErrVec.zero();
     773             :         }
     774      861141 :         if (fusePosData && posHealth) {
     775      360297 :             fuseData[3] = true;
     776      360297 :             fuseData[4] = true;
     777      360297 :             tiltErrVec.zero();
     778             :         }
     779      861141 :         if (fuseHgtData && hgtHealth) {
     780      613490 :             fuseData[5] = true;
     781             :         }
     782             : 
     783             :         // fuse measurements sequentially
     784     6027987 :         for (obsIndex=0; obsIndex<=5; obsIndex++) {
     785     5166846 :             if (fuseData[obsIndex]) {
     786     2094137 :                 stateIndex = 3 + obsIndex;
     787             :                 // calculate the measurement innovation, using states from a different time coordinate if fusing height data
     788             :                 // adjust scaling on GPS measurement noise variances if not enough satellites
     789     2094137 :                 if (obsIndex <= 2)
     790             :                 {
     791      760053 :                     innovVelPos[obsIndex] = stateStruct.velocity[obsIndex] - velPosObs[obsIndex];
     792      760053 :                     R_OBS[obsIndex] *= sq(gpsNoiseScaler);
     793             :                 }
     794     1334084 :                 else if (obsIndex == 3 || obsIndex == 4) {
     795      720594 :                     innovVelPos[obsIndex] = stateStruct.position[obsIndex-3] - velPosObs[obsIndex];
     796      720594 :                     R_OBS[obsIndex] *= sq(gpsNoiseScaler);
     797             :                 } else if (obsIndex == 5) {
     798      613490 :                     innovVelPos[obsIndex] = stateStruct.position[obsIndex-3] - velPosObs[obsIndex];
     799             :                     const ftype gndMaxBaroErr = 4.0f;
     800             :                     const ftype gndBaroInnovFloor = -0.5f;
     801             : 
     802      613490 :                     if(dal.get_touchdown_expected() && activeHgtSource == HGT_SOURCE_BARO) {
     803             :                         // when a touchdown is expected, floor the barometer innovation at gndBaroInnovFloor
     804             :                         // constrain the correction between 0 and gndBaroInnovFloor+gndMaxBaroErr
     805             :                         // this function looks like this:
     806             :                         //         |/
     807             :                         //---------|---------
     808             :                         //    ____/|
     809             :                         //   /     |
     810             :                         //  /      |
     811        2034 :                         innovVelPos[5] += constrain_ftype(-innovVelPos[5]+gndBaroInnovFloor, 0.0f, gndBaroInnovFloor+gndMaxBaroErr);
     812             :                     }
     813             :                 }
     814             : 
     815             :                 // calculate the Kalman gain and calculate innovation variances
     816     2094137 :                 varInnovVelPos[obsIndex] = P[stateIndex][stateIndex] + R_OBS[obsIndex];
     817     2094137 :                 SK = 1.0f/varInnovVelPos[obsIndex];
     818    35600329 :                 for (uint8_t i= 0; i<=15; i++) {
     819    33506192 :                     Kfusion[i] = P[i][stateIndex]*SK;
     820             :                 }
     821             : 
     822             :                 // inhibit magnetic field state estimation by setting Kalman gains to zero
     823     2094137 :                 if (!inhibitMagStates) {
     824    10699633 :                     for (uint8_t i = 16; i<=21; i++) {
     825     9171114 :                         Kfusion[i] = P[i][stateIndex]*SK;
     826             :                     }
     827             :                 } else {
     828     3959326 :                     for (uint8_t i = 16; i<=21; i++) {
     829     3393708 :                         Kfusion[i] = 0.0f;
     830             :                     }
     831             :                 }
     832             : 
     833             :                 // inhibit wind state estimation by setting Kalman gains to zero
     834     2094137 :                 if (!inhibitWindStates) {
     835     1419073 :                     Kfusion[22] = P[22][stateIndex]*SK;
     836     1419073 :                     Kfusion[23] = P[23][stateIndex]*SK;
     837             :                 } else {
     838      675064 :                     Kfusion[22] = 0.0f;
     839      675064 :                     Kfusion[23] = 0.0f;
     840             :                 }
     841             : 
     842             :                 // update the covariance - take advantage of direct observation of a single state at index = stateIndex to reduce computations
     843             :                 // this is a numerically optimised implementation of standard equation P = (I - K*H)*P;
     844    47692575 :                 for (uint8_t i= 0; i<=stateIndexLim; i++) {
     845  1063908026 :                     for (uint8_t j= 0; j<=stateIndexLim; j++)
     846             :                     {
     847  1018309588 :                         KHP[i][j] = Kfusion[i] * P[stateIndex][j];
     848             :                     }
     849             :                 }
     850             :                 // Check that we are not going to drive any variances negative and skip the update if so
     851             :                 bool healthyFusion = true;
     852    47692575 :                 for (uint8_t i= 0; i<=stateIndexLim; i++) {
     853    45598438 :                     if (KHP[i][i] > P[i][i]) {
     854             :                         healthyFusion = false;
     855             :                     }
     856             :                 }
     857     2094137 :                 if (healthyFusion) {
     858             :                     // update the covariance matrix
     859    47692507 :                     for (uint8_t i= 0; i<=stateIndexLim; i++) {
     860  1063906938 :                         for (uint8_t j= 0; j<=stateIndexLim; j++) {
     861  1018308564 :                             P[i][j] = P[i][j] - KHP[i][j];
     862             :                         }
     863             :                     }
     864             : 
     865             :                     // force the covariance matrix to be symmetrical and limit the variances to prevent ill-conditioning.
     866     2094133 :                     ForceSymmetry();
     867     2094133 :                     ConstrainVariances();
     868             : 
     869             :                     // update the states
     870             :                     // zero the attitude error state - by definition it is assumed to be zero before each observation fusion
     871     2094133 :                     stateStruct.angErr.zero();
     872             : 
     873             :                     // calculate state corrections and re-normalise the quaternions for states predicted using the blended IMU data
     874    47692507 :                     for (uint8_t i = 0; i<=stateIndexLim; i++) {
     875    45598374 :                         statesArray[i] = statesArray[i] - Kfusion[i] * innovVelPos[obsIndex];
     876             :                     }
     877             : 
     878             :                     // the first 3 states represent the angular misalignment vector.
     879             :                     // This is used to correct the estimated quaternion
     880     2094133 :                     stateStruct.quat.rotate(stateStruct.angErr);
     881             : 
     882             :                     // sum the attitude error from velocity and position fusion only
     883             :                     // used as a metric for convergence monitoring
     884     2094133 :                     if (obsIndex != 5) {
     885     1480643 :                         tiltErrVec += stateStruct.angErr;
     886             :                     }
     887             :                     // record good fusion status
     888     2094133 :                     if (obsIndex == 0) {
     889      253351 :                         faultStatus.bad_nvel = false;
     890     1840782 :                     } else if (obsIndex == 1) {
     891      253347 :                         faultStatus.bad_evel = false;
     892     1587435 :                     } else if (obsIndex == 2) {
     893      253351 :                         faultStatus.bad_dvel = false;
     894     1334084 :                     } else if (obsIndex == 3) {
     895      360297 :                         faultStatus.bad_npos = false;
     896      973787 :                     } else if (obsIndex == 4) {
     897      360297 :                         faultStatus.bad_epos = false;
     898             :                     } else if (obsIndex == 5) {
     899      613490 :                         faultStatus.bad_dpos = false;
     900             :                     }
     901             :                 } else {
     902             :                     // record bad fusion status
     903             :                     if (obsIndex == 0) {
     904           0 :                         faultStatus.bad_nvel = true;
     905             :                     } else if (obsIndex == 1) {
     906           4 :                         faultStatus.bad_evel = true;
     907             :                     } else if (obsIndex == 2) {
     908           0 :                         faultStatus.bad_dvel = true;
     909             :                     } else if (obsIndex == 3) {
     910           0 :                         faultStatus.bad_npos = true;
     911             :                     } else if (obsIndex == 4) {
     912           0 :                         faultStatus.bad_epos = true;
     913             :                     } else if (obsIndex == 5) {
     914           0 :                         faultStatus.bad_dpos = true;
     915             :                     }
     916             :                 }
     917             :             }
     918             :         }
     919             :     }
     920      861141 : }
     921             : 
     922             : /********************************************************
     923             : *                   MISC FUNCTIONS                      *
     924             : ********************************************************/
     925             : 
     926             : // select the height measurement to be fused from the available baro, range finder and GPS sources
     927     3230372 : void NavEKF2_core::selectHeightForFusion()
     928             : {
     929             : #if AP_RANGEFINDER_ENABLED
     930             :     // Read range finder data and check for new data in the buffer
     931             :     // This data is used by both height and optical flow fusion processing
     932     3230372 :     readRangeFinder();
     933     3230372 :     rangeDataToFuse = storedRange.recall(rangeDataDelayed,imuDataDelayed.time_ms);
     934             : 
     935             :     // correct range data for the body frame position offset relative to the IMU
     936             :     // the corrected reading is the reading that would have been taken if the sensor was
     937             :     // co-located with the IMU
     938     3230372 :     const auto *_rng = dal.rangefinder();
     939     3230372 :     if (_rng && rangeDataToFuse) {
     940        3096 :         const auto *sensor = _rng->get_backend(rangeDataDelayed.sensor_idx);
     941        3096 :         if (sensor != nullptr) {
     942        3096 :             Vector3F posOffsetBody = sensor->get_pos_offset().toftype() - accelPosOffset;
     943        3096 :             if (!posOffsetBody.is_zero()) {
     944        1112 :                 Vector3F posOffsetEarth = prevTnb.mul_transpose(posOffsetBody);
     945        1112 :                 rangeDataDelayed.rng += posOffsetEarth.z / prevTnb.c.z;
     946             :             }
     947             :         }
     948             :     }
     949             : #endif
     950             : 
     951             :     // read baro height data from the sensor and check for new data in the buffer
     952     3230372 :     readBaroData();
     953     3230372 :     baroDataToFuse = storedBaro.recall(baroDataDelayed, imuDataDelayed.time_ms);
     954             : 
     955     3230372 :     bool rangeFinderDataIsFresh = (imuSampleTime_ms - rngValidMeaTime_ms < 500);
     956             :     // select height source
     957     3230372 :     if (extNavUsedForPos) {
     958             :         // always use external navigation as the height source if using for position.
     959           0 :         activeHgtSource = HGT_SOURCE_EXTNAV;
     960             : #if AP_RANGEFINDER_ENABLED
     961     3230372 :     } else if ((frontend->_altSource == 1) && _rng && rangeFinderDataIsFresh) {
     962             :         // user has specified the range finder as a primary height source
     963           0 :         activeHgtSource = HGT_SOURCE_RNG;
     964     3230372 :     } else if ((frontend->_useRngSwHgt > 0) && ((frontend->_altSource == 0) || (frontend->_altSource == 2)) && _rng && rangeFinderDataIsFresh) {
     965             :         // determine if we are above or below the height switch region
     966           0 :         const ftype rangeMaxUse = 1e-2f * _rng->max_distance_orient(ROTATION_PITCH_270) * (ftype)frontend->_useRngSwHgt;
     967           0 :         bool aboveUpperSwHgt = (terrainState - stateStruct.position.z) > rangeMaxUse;
     968           0 :         bool belowLowerSwHgt = (terrainState - stateStruct.position.z) < 0.7f * rangeMaxUse;
     969             : 
     970             :         // If the terrain height is consistent and we are moving slowly, then it can be
     971             :         // used as a height reference in combination with a range finder
     972             :         // apply a hysteresis to the speed check to prevent rapid switching
     973           0 :         ftype horizSpeed = stateStruct.velocity.xy().length();
     974           0 :         bool dontTrustTerrain = ((horizSpeed > frontend->_useRngSwSpd) && filterStatus.flags.horiz_vel) || !terrainHgtStable;
     975           0 :         ftype trust_spd_trigger = MAX((frontend->_useRngSwSpd - 1.0f),(frontend->_useRngSwSpd * 0.5f));
     976           0 :         bool trustTerrain = (horizSpeed < trust_spd_trigger) && terrainHgtStable;
     977             : 
     978             :         /*
     979             :             * Switch between range finder and primary height source using height above ground and speed thresholds with
     980             :             * hysteresis to avoid rapid switching. Using range finder for height requires a consistent terrain height
     981             :             * which cannot be assumed if the vehicle is moving horizontally.
     982             :         */
     983           0 :         if ((aboveUpperSwHgt || dontTrustTerrain) && (activeHgtSource == HGT_SOURCE_RNG)) {
     984             :             // cannot trust terrain or range finder so stop using range finder height
     985           0 :             if (frontend->_altSource == 0) {
     986           0 :                 activeHgtSource = HGT_SOURCE_BARO;
     987           0 :             } else if (frontend->_altSource == 2) {
     988           0 :                 activeHgtSource = HGT_SOURCE_GPS;
     989             :             }
     990           0 :         } else if (belowLowerSwHgt && trustTerrain && (prevTnb.c.z >= 0.7f)) {
     991             :             // reliable terrain and range finder so start using range finder height
     992           0 :             activeHgtSource = HGT_SOURCE_RNG;
     993             :         }
     994             : #endif  // AP_RANGEFINDER_ENABLED
     995     3230372 :     } else if (frontend->_altSource == 0) {
     996     3230372 :         activeHgtSource = HGT_SOURCE_BARO;
     997           0 :     } else if ((frontend->_altSource == 2) && ((imuSampleTime_ms - lastTimeGpsReceived_ms) < 500) && validOrigin && gpsAccuracyGood) {
     998           0 :         activeHgtSource = HGT_SOURCE_GPS;
     999           0 :     } else if ((frontend->_altSource == 3) && validOrigin && rngBcnGoodToAlign) {
    1000           0 :         activeHgtSource = HGT_SOURCE_BCN;
    1001             :     }
    1002             : 
    1003             :     // Use Baro alt as a fallback if we lose range finder, GPS, external nav or Beacon
    1004     3230372 :     bool lostRngHgt = ((activeHgtSource == HGT_SOURCE_RNG) && (!rangeFinderDataIsFresh));
    1005     3230372 :     bool lostGpsHgt = ((activeHgtSource == HGT_SOURCE_GPS) && ((imuSampleTime_ms - lastTimeGpsReceived_ms) > 2000));
    1006     3230372 :     bool lostExtNavHgt = ((activeHgtSource == HGT_SOURCE_EXTNAV) && ((imuSampleTime_ms - extNavMeasTime_ms) > 2000));
    1007     3230372 :     bool lostRngBcnHgt = ((activeHgtSource == HGT_SOURCE_BCN) && ((imuSampleTime_ms - rngBcnDataDelayed.time_ms) > 2000));
    1008     3230372 :     if (lostRngHgt || lostGpsHgt || lostExtNavHgt || lostRngBcnHgt) {
    1009           0 :         activeHgtSource = HGT_SOURCE_BARO;
    1010             :     }
    1011             : 
    1012             :     // if there is new baro data to fuse, calculate filtered baro data required by other processes
    1013     3230372 :     if (baroDataToFuse) {
    1014             :         // calculate offset to baro data that enables us to switch to Baro height use during operation
    1015      614058 :         if  (activeHgtSource != HGT_SOURCE_BARO) {
    1016           0 :             calcFiltBaroOffset();
    1017             :         }
    1018             :         // filtered baro data used to provide a reference for takeoff
    1019             :         // it is is reset to last height measurement on disarming in performArmingChecks()
    1020      614058 :         if (!dal.get_takeoff_expected()) {
    1021             :             const ftype gndHgtFiltTC = 0.5f;
    1022      609244 :             const ftype dtBaro = frontend->hgtAvg_ms*1.0e-3;
    1023      609244 :             ftype alpha = constrain_ftype(dtBaro / (dtBaro+gndHgtFiltTC),0.0f,1.0f);
    1024      609244 :             meaHgtAtTakeOff += (baroDataDelayed.hgt-meaHgtAtTakeOff)*alpha;
    1025             :         }
    1026             :     }
    1027             : 
    1028             :     // If we are not using GPS as the primary height sensor, correct EKF origin height so that
    1029             :     // combined local NED position height and origin height remains consistent with the GPS altitude
    1030             :     // This also enables the GPS height to be used as a backup height source
    1031     3230372 :     if (gpsDataToFuse &&
    1032      287618 :             (((frontend->_originHgtMode & (1 << 0)) && (activeHgtSource == HGT_SOURCE_BARO)) ||
    1033      287618 :             ((frontend->_originHgtMode & (1 << 1)) && (activeHgtSource == HGT_SOURCE_RNG)))
    1034             :             ) {
    1035           0 :         correctEkfOriginHeight();
    1036             :     }
    1037             : 
    1038             :     // Select the height measurement source
    1039     3230372 :     if (extNavDataToFuse && (activeHgtSource == HGT_SOURCE_EXTNAV)) {
    1040           0 :         hgtMea = -extNavDataDelayed.pos.z;
    1041           0 :         posDownObsNoise = sq(constrain_ftype(extNavDataDelayed.posErr, 0.01f, 10.0f));
    1042     3230372 :     } else if (rangeDataToFuse && (activeHgtSource == HGT_SOURCE_RNG)) {
    1043             :         // using range finder data
    1044             :         // correct for tilt using a flat earth model
    1045           0 :         if (prevTnb.c.z >= 0.7) {
    1046             :             // calculate height above ground
    1047           0 :             hgtMea  = MAX(rangeDataDelayed.rng * prevTnb.c.z, rngOnGnd);
    1048             :             // correct for terrain position relative to datum
    1049           0 :             hgtMea -= terrainState;
    1050             :             // enable fusion
    1051           0 :             fuseHgtData = true;
    1052           0 :             velPosObs[5] = -hgtMea;
    1053             :             // set the observation noise
    1054           0 :             posDownObsNoise = sq(constrain_ftype(frontend->_rngNoise, 0.1f, 10.0f));
    1055             :             // add uncertainty created by terrain gradient and vehicle tilt
    1056           0 :             posDownObsNoise += sq(rangeDataDelayed.rng * frontend->_terrGradMax) * MAX(0.0f , (1.0f - sq(prevTnb.c.z)));
    1057             :         } else {
    1058             :             // disable fusion if tilted too far
    1059           0 :             fuseHgtData = false;
    1060             :         }
    1061     3230372 :     } else if  (gpsDataToFuse && (activeHgtSource == HGT_SOURCE_GPS)) {
    1062             :         // using GPS data
    1063           0 :         hgtMea = gpsDataDelayed.hgt;
    1064             :         // enable fusion
    1065           0 :         velPosObs[5] = -hgtMea;
    1066           0 :         fuseHgtData = true;
    1067             :         // set the observation noise using receiver reported accuracy or the horizontal noise scaled for typical VDOP/HDOP ratio
    1068           0 :         if (gpsHgtAccuracy > 0.0f) {
    1069           0 :             posDownObsNoise = sq(constrain_ftype(gpsHgtAccuracy, 1.5f * frontend->_gpsHorizPosNoise, 100.0f));
    1070             :         } else {
    1071           0 :             posDownObsNoise = sq(constrain_ftype(1.5f * frontend->_gpsHorizPosNoise, 0.1f, 10.0f));
    1072             :         }
    1073     3230372 :     } else if (baroDataToFuse && (activeHgtSource == HGT_SOURCE_BARO)) {
    1074             :         // using Baro data
    1075      614058 :         hgtMea = baroDataDelayed.hgt - baroHgtOffset;
    1076             :         // enable fusion
    1077      614058 :         velPosObs[5] = -hgtMea;
    1078      614058 :         fuseHgtData = true;
    1079             :         // set the observation noise
    1080      614058 :         posDownObsNoise = sq(constrain_ftype(frontend->_baroAltNoise, 0.1f, 10.0f));
    1081             :         // reduce weighting (increase observation noise) on baro if we are likely to be in ground effect
    1082      614058 :         if (dal.get_takeoff_expected() || dal.get_touchdown_expected()) {
    1083        6456 :             posDownObsNoise *= frontend->gndEffectBaroScaler;
    1084             :         }
    1085             :         // If we are in takeoff mode, the height measurement is limited to be no less than the measurement at start of takeoff
    1086             :         // This prevents negative baro disturbances due to copter downwash corrupting the EKF altitude during initial ascent
    1087      614058 :         if (motorsArmed && dal.get_takeoff_expected() && !assume_zero_sideslip()) {
    1088        1224 :             hgtMea = MAX(hgtMea, meaHgtAtTakeOff);
    1089             :         }
    1090             :     } else {
    1091     2616314 :         fuseHgtData = false;
    1092             :     }
    1093             : 
    1094             :     // If we haven't fused height data for a while, then declare the height data as being timed out
    1095             :     // set timeout period based on whether we have vertical GPS velocity available to constrain drift
    1096     3230372 :     hgtRetryTime_ms = ((useGpsVertVel || useExtNavVel) && !velTimeout) ? frontend->hgtRetryTimeMode0_ms : frontend->hgtRetryTimeMode12_ms;
    1097     3230372 :     if (imuSampleTime_ms - lastHgtPassTime_ms > hgtRetryTime_ms) {
    1098         712 :         hgtTimeout = true;
    1099             :     } else {
    1100     3229660 :         hgtTimeout = false;
    1101             :     }
    1102     3230372 : }
    1103             : 

Generated by: LCOV version 1.14