LCOV - code coverage report
Current view: top level - AP_NavEKF2 - AP_NavEKF2_RngBcnFusion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 4 289 1.4 %
Date: 2026-09-09 11:02:08 Functions: 1 4 25.0 %

          Line data    Source code
       1             : #include "AP_NavEKF2.h"
       2             : #include "AP_NavEKF2_core.h"
       3             : 
       4             : /********************************************************
       5             : *                   FUSE MEASURED_DATA                  *
       6             : ********************************************************/
       7             : 
       8             : #if AP_BEACON_ENABLED
       9             : // select fusion of range beacon measurements
      10     3248540 : void NavEKF2_core::SelectRngBcnFusion()
      11             : {
      12             :     // read range data from the sensor and check for new data in the buffer
      13     3248540 :     readRngBcnData();
      14             : 
      15             :     // Determine if we need to fuse range beacon data on this time step
      16     3248540 :     if (rngBcnDataToFuse) {
      17           0 :         if (PV_AidingMode == AID_ABSOLUTE) {
      18             :             // Normal operating mode is to fuse the range data into the main filter
      19           0 :             FuseRngBcn();
      20             :         } else {
      21             :             // If we aren't able to use the data in the main filter, use a simple 3-state filter to estimte position only
      22           0 :             FuseRngBcnStatic();
      23             :         }
      24             :     }
      25     3248540 : }
      26             : 
      27           0 : void NavEKF2_core::FuseRngBcn()
      28             : {
      29             :     // declarations
      30             :     ftype pn;
      31             :     ftype pe;
      32             :     ftype pd;
      33             :     ftype bcn_pn;
      34             :     ftype bcn_pe;
      35             :     ftype bcn_pd;
      36           0 :     const ftype R_BCN = sq(MAX(rngBcnDataDelayed.rngErr , 0.1f));
      37             :     ftype rngPred;
      38             : 
      39             :     // health is set bad until test passed
      40           0 :     rngBcnHealth = false;
      41             : 
      42           0 :     if (activeHgtSource != HGT_SOURCE_BCN) {
      43             :         // calculate the vertical offset from EKF datum to beacon datum
      44           0 :         CalcRangeBeaconPosDownOffset(R_BCN, stateStruct.position, false);
      45             :     } else {
      46           0 :         bcnPosOffset = 0.0f;
      47             :     }
      48             : 
      49             :     // copy required states to local variable names
      50           0 :     pn = stateStruct.position.x;
      51           0 :     pe = stateStruct.position.y;
      52           0 :     pd = stateStruct.position.z;
      53           0 :     bcn_pn = rngBcnDataDelayed.beacon_posNED.x;
      54           0 :     bcn_pe = rngBcnDataDelayed.beacon_posNED.y;
      55           0 :     bcn_pd = rngBcnDataDelayed.beacon_posNED.z + bcnPosOffset;
      56             : 
      57             :     // predicted range
      58           0 :     Vector3F deltaPosNED = stateStruct.position - rngBcnDataDelayed.beacon_posNED;
      59           0 :     rngPred = deltaPosNED.length();
      60             : 
      61             :     // calculate measurement innovation
      62           0 :     innovRngBcn = rngPred - rngBcnDataDelayed.rng;
      63             : 
      64             :     // perform fusion of range measurement
      65           0 :     if (rngPred > 0.1f)
      66             :     {
      67             :         // calculate observation jacobians
      68           0 :         ftype H_BCN[24] = {};
      69           0 :         ftype t2 = bcn_pd-pd;
      70           0 :         ftype t3 = bcn_pe-pe;
      71           0 :         ftype t4 = bcn_pn-pn;
      72           0 :         ftype t5 = t2*t2;
      73           0 :         ftype t6 = t3*t3;
      74           0 :         ftype t7 = t4*t4;
      75           0 :         ftype t8 = t5+t6+t7;
      76           0 :         ftype t9 = 1.0f/sqrtF(t8);
      77           0 :         H_BCN[6] = -t4*t9;
      78           0 :         H_BCN[7] = -t3*t9;
      79           0 :         H_BCN[8] = -t2*t9;
      80             : 
      81             :         // calculate Kalman gains
      82           0 :         ftype t10 = P[8][8]*t2*t9;
      83           0 :         ftype t11 = P[7][8]*t3*t9;
      84           0 :         ftype t12 = P[6][8]*t4*t9;
      85           0 :         ftype t13 = t10+t11+t12;
      86           0 :         ftype t14 = t2*t9*t13;
      87           0 :         ftype t15 = P[8][7]*t2*t9;
      88           0 :         ftype t16 = P[7][7]*t3*t9;
      89           0 :         ftype t17 = P[6][7]*t4*t9;
      90           0 :         ftype t18 = t15+t16+t17;
      91           0 :         ftype t19 = t3*t9*t18;
      92           0 :         ftype t20 = P[8][6]*t2*t9;
      93           0 :         ftype t21 = P[7][6]*t3*t9;
      94           0 :         ftype t22 = P[6][6]*t4*t9;
      95           0 :         ftype t23 = t20+t21+t22;
      96           0 :         ftype t24 = t4*t9*t23;
      97           0 :         varInnovRngBcn = R_BCN+t14+t19+t24;
      98             :         ftype t26;
      99           0 :         if (varInnovRngBcn >= R_BCN) {
     100           0 :             t26 = 1.0/varInnovRngBcn;
     101           0 :             faultStatus.bad_rngbcn = false;
     102             :         } else {
     103             :             // the calculation is badly conditioned, so we cannot perform fusion on this step
     104             :             // we reset the covariance matrix and try again next measurement
     105           0 :             CovarianceInit();
     106           0 :             faultStatus.bad_rngbcn = true;
     107           0 :             return;
     108             :         }
     109             : 
     110           0 :         Kfusion[0] = -t26*(P[0][6]*t4*t9+P[0][7]*t3*t9+P[0][8]*t2*t9);
     111           0 :         Kfusion[1] = -t26*(P[1][6]*t4*t9+P[1][7]*t3*t9+P[1][8]*t2*t9);
     112           0 :         Kfusion[2] = -t26*(P[2][6]*t4*t9+P[2][7]*t3*t9+P[2][8]*t2*t9);
     113           0 :         Kfusion[3] = -t26*(P[3][6]*t4*t9+P[3][7]*t3*t9+P[3][8]*t2*t9);
     114           0 :         Kfusion[4] = -t26*(P[4][6]*t4*t9+P[4][7]*t3*t9+P[4][8]*t2*t9);
     115           0 :         Kfusion[6] = -t26*(t22+P[6][7]*t3*t9+P[6][8]*t2*t9);
     116           0 :         Kfusion[7] = -t26*(t16+P[7][6]*t4*t9+P[7][8]*t2*t9);
     117           0 :         if (activeHgtSource == HGT_SOURCE_BCN) {
     118             :             // We are using the range beacon as the primary height reference, so allow it to modify the EKF's vertical states
     119           0 :             Kfusion[5] = -t26*(P[5][6]*t4*t9+P[5][7]*t3*t9+P[5][8]*t2*t9);
     120           0 :             Kfusion[8] = -t26*(t10+P[8][6]*t4*t9+P[8][7]*t3*t9);
     121           0 :             Kfusion[15] = -t26*(P[15][6]*t4*t9+P[15][7]*t3*t9+P[15][8]*t2*t9);
     122           0 :             bcnPosOffset = 0.0f;
     123             : 
     124             :         } else {
     125             :             // don't allow the range measurement to affect the vertical states in the main filter
     126           0 :             Kfusion[5] = 0.0f;
     127           0 :             Kfusion[8] = 0.0f;
     128           0 :             Kfusion[15] = 0.0f;
     129             : 
     130             :         }
     131           0 :         Kfusion[9] = -t26*(P[9][6]*t4*t9+P[9][7]*t3*t9+P[9][8]*t2*t9);
     132           0 :         Kfusion[10] = -t26*(P[10][6]*t4*t9+P[10][7]*t3*t9+P[10][8]*t2*t9);
     133           0 :         Kfusion[11] = -t26*(P[11][6]*t4*t9+P[11][7]*t3*t9+P[11][8]*t2*t9);
     134           0 :         Kfusion[12] = -t26*(P[12][6]*t4*t9+P[12][7]*t3*t9+P[12][8]*t2*t9);
     135           0 :         Kfusion[13] = -t26*(P[13][6]*t4*t9+P[13][7]*t3*t9+P[13][8]*t2*t9);
     136           0 :         Kfusion[14] = -t26*(P[14][6]*t4*t9+P[14][7]*t3*t9+P[14][8]*t2*t9);
     137           0 :         if (!inhibitMagStates) {
     138           0 :             Kfusion[16] = -t26*(P[16][6]*t4*t9+P[16][7]*t3*t9+P[16][8]*t2*t9);
     139           0 :             Kfusion[17] = -t26*(P[17][6]*t4*t9+P[17][7]*t3*t9+P[17][8]*t2*t9);
     140           0 :             Kfusion[18] = -t26*(P[18][6]*t4*t9+P[18][7]*t3*t9+P[18][8]*t2*t9);
     141           0 :             Kfusion[19] = -t26*(P[19][6]*t4*t9+P[19][7]*t3*t9+P[19][8]*t2*t9);
     142           0 :             Kfusion[20] = -t26*(P[20][6]*t4*t9+P[20][7]*t3*t9+P[20][8]*t2*t9);
     143           0 :             Kfusion[21] = -t26*(P[21][6]*t4*t9+P[21][7]*t3*t9+P[21][8]*t2*t9);
     144             :         } else {
     145             :             // zero indexes 16 to 21 = 6
     146           0 :             zero_range(&Kfusion[0], 16, 21);
     147             :         }
     148           0 :         Kfusion[22] = -t26*(P[22][6]*t4*t9+P[22][7]*t3*t9+P[22][8]*t2*t9);
     149           0 :         Kfusion[23] = -t26*(P[23][6]*t4*t9+P[23][7]*t3*t9+P[23][8]*t2*t9);
     150             : 
     151             :         // Calculate innovation using the selected offset value
     152           0 :         Vector3F delta = stateStruct.position - rngBcnDataDelayed.beacon_posNED;
     153           0 :         innovRngBcn = delta.length() - rngBcnDataDelayed.rng;
     154             : 
     155             :         // calculate the innovation consistency test ratio
     156           0 :         rngBcnTestRatio = sq(innovRngBcn) / (sq(MAX(0.01f * (ftype)frontend->_rngBcnInnovGate, 1.0f)) * varInnovRngBcn);
     157             : 
     158             :         // fail if the ratio is > 1, but don't fail if bad IMU data
     159           0 :         rngBcnHealth = ((rngBcnTestRatio < 1.0f) || badIMUdata);
     160             : 
     161             :         // test the ratio before fusing data
     162           0 :         if (rngBcnHealth) {
     163             : 
     164             :             // restart the counter
     165           0 :             lastRngBcnPassTime_ms = imuSampleTime_ms;
     166             : 
     167             :             // correct the covariance P = (I - K*H)*P
     168             :             // take advantage of the empty columns in KH to reduce the
     169             :             // number of operations
     170           0 :             for (unsigned i = 0; i<=stateIndexLim; i++) {
     171           0 :                 for (unsigned j = 0; j<=5; j++) {
     172           0 :                     KH[i][j] = 0.0f;
     173             :                 }
     174           0 :                 for (unsigned j = 6; j<=8; j++) {
     175           0 :                     KH[i][j] = Kfusion[i] * H_BCN[j];
     176             :                 }
     177           0 :                 for (unsigned j = 9; j<=23; j++) {
     178           0 :                     KH[i][j] = 0.0f;
     179             :                 }
     180             :             }
     181           0 :             for (unsigned j = 0; j<=stateIndexLim; j++) {
     182           0 :                 for (unsigned i = 0; i<=stateIndexLim; i++) {
     183             :                     ftype res = 0;
     184           0 :                     res += KH[i][6] * P[6][j];
     185           0 :                     res += KH[i][7] * P[7][j];
     186           0 :                     res += KH[i][8] * P[8][j];
     187           0 :                     KHP[i][j] = res;
     188             :                 }
     189             :             }
     190             :             // Check that we are not going to drive any variances negative and skip the update if so
     191             :             bool healthyFusion = true;
     192           0 :             for (uint8_t i= 0; i<=stateIndexLim; i++) {
     193           0 :                 if (KHP[i][i] > P[i][i]) {
     194             :                     healthyFusion = false;
     195             :                 }
     196             :             }
     197           0 :             if (healthyFusion) {
     198             :                 // update the covariance matrix
     199           0 :                 for (uint8_t i= 0; i<=stateIndexLim; i++) {
     200           0 :                     for (uint8_t j= 0; j<=stateIndexLim; j++) {
     201           0 :                         P[i][j] = P[i][j] - KHP[i][j];
     202             :                     }
     203             :                 }
     204             : 
     205             :                 // force the covariance matrix to be symmetrical and limit the variances to prevent ill-conditioning.
     206           0 :                 ForceSymmetry();
     207           0 :                 ConstrainVariances();
     208             : 
     209             :                 // update the states
     210             :                 // zero the attitude error state - by definition it is assumed to be zero before each observation fusion
     211           0 :                 stateStruct.angErr.zero();
     212             : 
     213             :                 // correct the state vector
     214           0 :                 for (uint8_t j= 0; j<=stateIndexLim; j++) {
     215           0 :                     statesArray[j] = statesArray[j] - Kfusion[j] * innovRngBcn;
     216             :                 }
     217             : 
     218             :                 // the first 3 states represent the angular misalignment vector.
     219             :                 // This is used to correct the estimated quaternion on the current time step
     220           0 :                 stateStruct.quat.rotate(stateStruct.angErr);
     221             : 
     222             :                 // record healthy fusion
     223           0 :                 faultStatus.bad_rngbcn = false;
     224             : 
     225             :             } else {
     226             :                 // record bad fusion
     227           0 :                 faultStatus.bad_rngbcn = true;
     228             : 
     229             :             }
     230             :         }
     231             : 
     232             :         // Update the fusion report
     233           0 :         rngBcnFusionReport[rngBcnDataDelayed.beacon_ID].beaconPosNED = rngBcnDataDelayed.beacon_posNED;
     234           0 :         rngBcnFusionReport[rngBcnDataDelayed.beacon_ID].innov = innovRngBcn;
     235           0 :         rngBcnFusionReport[rngBcnDataDelayed.beacon_ID].innovVar = varInnovRngBcn;
     236           0 :         rngBcnFusionReport[rngBcnDataDelayed.beacon_ID].rng = rngBcnDataDelayed.rng;
     237           0 :         rngBcnFusionReport[rngBcnDataDelayed.beacon_ID].testRatio = rngBcnTestRatio;
     238             :     }
     239             : }
     240             : 
     241             : /*
     242             : Use range beacon measurements to calculate a static position using a 3-state EKF algorithm.
     243             : Algorithm based on the following:
     244             : https://github.com/priseborough/InertialNav/blob/master/derivations/range_beacon.m
     245             : */
     246           0 : void NavEKF2_core::FuseRngBcnStatic()
     247             : {
     248             :     // get the estimated range measurement variance
     249           0 :     const ftype R_RNG = sq(MAX(rngBcnDataDelayed.rngErr , 0.1f));
     250             : 
     251             :     /*
     252             :     The first thing to do is to check if we have started the alignment and if not, initialise the
     253             :     states and covariance to a first guess. To do this iterate through the available beacons and then
     254             :     initialise the initial position to the mean beacon position. The initial position uncertainty
     255             :     is set to the mean range measurement.
     256             :     */
     257           0 :     if (!rngBcnAlignmentStarted) {
     258           0 :         if (rngBcnDataDelayed.beacon_ID != lastBeaconIndex) {
     259           0 :             rngBcnPosSum += rngBcnDataDelayed.beacon_posNED;
     260           0 :             lastBeaconIndex = rngBcnDataDelayed.beacon_ID;
     261           0 :             rngSum += rngBcnDataDelayed.rng;
     262           0 :             numBcnMeas++;
     263             : 
     264             :             // capture the beacon vertical spread
     265           0 :             if (rngBcnDataDelayed.beacon_posNED.z > maxBcnPosD) {
     266           0 :                 maxBcnPosD = rngBcnDataDelayed.beacon_posNED.z;
     267           0 :             } else if(rngBcnDataDelayed.beacon_posNED.z < minBcnPosD) {
     268           0 :                 minBcnPosD = rngBcnDataDelayed.beacon_posNED.z;
     269             :             }
     270             :         }
     271           0 :         if (numBcnMeas >= 100) {
     272           0 :             rngBcnAlignmentStarted = true;
     273           0 :             ftype tempVar = 1.0f / (float)numBcnMeas;
     274             :             // initialise the receiver position to the centre of the beacons and at zero height
     275           0 :             receiverPos.x = rngBcnPosSum.x * tempVar;
     276           0 :             receiverPos.y = rngBcnPosSum.y * tempVar;
     277           0 :             receiverPos.z = 0.0f;
     278           0 :             receiverPosCov[2][2] = receiverPosCov[1][1] = receiverPosCov[0][0] = rngSum * tempVar;
     279           0 :             lastBeaconIndex  = 0;
     280           0 :             numBcnMeas = 0;
     281           0 :             rngBcnPosSum.zero();
     282           0 :             rngSum = 0.0f;
     283             :         }
     284             :     }
     285             : 
     286           0 :     if (rngBcnAlignmentStarted) {
     287           0 :         numBcnMeas++;
     288             : 
     289           0 :         if (numBcnMeas >= 100) {
     290             :             // 100 observations is enough for a stable estimate under most conditions
     291             :             // TODO monitor stability of the position estimate
     292           0 :             rngBcnAlignmentCompleted = true;
     293             : 
     294             :         }
     295             : 
     296           0 :         if (rngBcnAlignmentCompleted) {
     297           0 :             if (activeHgtSource != HGT_SOURCE_BCN) {
     298             :                 // We are using a different height reference for the main EKF so need to estimate a vertical
     299             :                 // position offset to be applied to the beacon system that minimises the range innovations
     300             :                 // The position estimate should be stable after 100 iterations so we use a simple dual
     301             :                 // hypothesis 1-state EKF to estimate the offset
     302             :                 Vector3F refPosNED;
     303           0 :                 refPosNED.x = receiverPos.x;
     304           0 :                 refPosNED.y = receiverPos.y;
     305           0 :                 refPosNED.z = stateStruct.position.z;
     306           0 :                 CalcRangeBeaconPosDownOffset(R_RNG, refPosNED, true);
     307             : 
     308             :             } else {
     309             :                 // we are using the beacons as the primary height source, so don't modify their vertical position
     310           0 :                 bcnPosOffset = 0.0f;
     311             : 
     312             :             }
     313             :         } else {
     314           0 :             if (activeHgtSource != HGT_SOURCE_BCN) {
     315             :                 // The position estimate is not yet stable so we cannot run the 1-state EKF to estimate
     316             :                 // beacon system vertical position offset. Instead we initialise the dual hypothesis offset states
     317             :                 // using the beacon vertical position, vertical position estimate relative to the beacon origin
     318             :                 // and the main EKF vertical position
     319             : 
     320             :                 // Calculate the mid vertical position of all beacons
     321           0 :                 ftype bcnMidPosD = 0.5f * (minBcnPosD + maxBcnPosD);
     322             : 
     323             :                 // calculate the delta to the estimated receiver position
     324           0 :                 ftype delta = receiverPos.z - bcnMidPosD;
     325             : 
     326             :                 // calculate the two hypothesis for our vertical position
     327             :                 ftype receiverPosDownMax;
     328             :                 ftype receiverPosDownMin;
     329           0 :                 if (delta >= 0.0f) {
     330             :                     receiverPosDownMax = receiverPos.z;
     331           0 :                     receiverPosDownMin = receiverPos.z - 2.0f * delta;
     332             :                 } else {
     333           0 :                     receiverPosDownMax = receiverPos.z - 2.0f * delta;
     334             :                     receiverPosDownMin = receiverPos.z;
     335             :                 }
     336             : 
     337           0 :                 bcnPosOffsetMax = stateStruct.position.z - receiverPosDownMin;
     338           0 :                 bcnPosOffsetMin = stateStruct.position.z - receiverPosDownMax;
     339             :             } else {
     340             :                 // We are using the beacons as the primary height reference, so don't modify their vertical position
     341           0 :                 bcnPosOffset = 0.0f;
     342             :             }
     343             :         }
     344             : 
     345             :         // Add some process noise to the states at each time step
     346           0 :         for (uint8_t i= 0; i<=2; i++) {
     347           0 :             receiverPosCov[i][i] += 0.1f;
     348             :         }
     349             : 
     350             :         // calculate the observation jacobian
     351           0 :         ftype t2 = rngBcnDataDelayed.beacon_posNED.z - receiverPos.z + bcnPosOffset;
     352           0 :         ftype t3 = rngBcnDataDelayed.beacon_posNED.y - receiverPos.y;
     353           0 :         ftype t4 = rngBcnDataDelayed.beacon_posNED.x - receiverPos.x;
     354           0 :         ftype t5 = t2*t2;
     355           0 :         ftype t6 = t3*t3;
     356           0 :         ftype t7 = t4*t4;
     357           0 :         ftype t8 = t5+t6+t7;
     358           0 :         if (t8 < 0.1f) {
     359             :             // calculation will be badly conditioned
     360           0 :             return;
     361             :         }
     362           0 :         ftype t9 = 1.0f/sqrtF(t8);
     363           0 :         ftype t10 = rngBcnDataDelayed.beacon_posNED.x*2.0f;
     364           0 :         ftype t15 = receiverPos.x*2.0f;
     365           0 :         ftype t11 = t10-t15;
     366           0 :         ftype t12 = rngBcnDataDelayed.beacon_posNED.y*2.0f;
     367           0 :         ftype t14 = receiverPos.y*2.0f;
     368           0 :         ftype t13 = t12-t14;
     369           0 :         ftype t16 = rngBcnDataDelayed.beacon_posNED.z*2.0f;
     370           0 :         ftype t18 = receiverPos.z*2.0f;
     371           0 :         ftype t17 = t16-t18;
     372             :         ftype H_RNG[3];
     373           0 :         H_RNG[0] = -t9*t11*0.5f;
     374           0 :         H_RNG[1] = -t9*t13*0.5f;
     375           0 :         H_RNG[2] = -t9*t17*0.5f;
     376             : 
     377             :         // calculate the Kalman gains
     378           0 :         ftype t19 = receiverPosCov[0][0]*t9*t11*0.5f;
     379           0 :         ftype t20 = receiverPosCov[1][1]*t9*t13*0.5f;
     380           0 :         ftype t21 = receiverPosCov[0][1]*t9*t11*0.5f;
     381           0 :         ftype t22 = receiverPosCov[2][1]*t9*t17*0.5f;
     382           0 :         ftype t23 = t20+t21+t22;
     383           0 :         ftype t24 = t9*t13*t23*0.5f;
     384           0 :         ftype t25 = receiverPosCov[1][2]*t9*t13*0.5f;
     385           0 :         ftype t26 = receiverPosCov[0][2]*t9*t11*0.5f;
     386           0 :         ftype t27 = receiverPosCov[2][2]*t9*t17*0.5f;
     387           0 :         ftype t28 = t25+t26+t27;
     388           0 :         ftype t29 = t9*t17*t28*0.5f;
     389           0 :         ftype t30 = receiverPosCov[1][0]*t9*t13*0.5f;
     390           0 :         ftype t31 = receiverPosCov[2][0]*t9*t17*0.5f;
     391           0 :         ftype t32 = t19+t30+t31;
     392           0 :         ftype t33 = t9*t11*t32*0.5f;
     393           0 :         varInnovRngBcn = R_RNG+t24+t29+t33;
     394           0 :         ftype t35 = 1.0f/varInnovRngBcn;
     395             :         ftype K_RNG[3];
     396           0 :         K_RNG[0] = -t35*(t19+receiverPosCov[0][1]*t9*t13*0.5f+receiverPosCov[0][2]*t9*t17*0.5f);
     397           0 :         K_RNG[1] = -t35*(t20+receiverPosCov[1][0]*t9*t11*0.5f+receiverPosCov[1][2]*t9*t17*0.5f);
     398           0 :         K_RNG[2] = -t35*(t27+receiverPosCov[2][0]*t9*t11*0.5f+receiverPosCov[2][1]*t9*t13*0.5f);
     399             : 
     400             :         // calculate range measurement innovation
     401           0 :         Vector3F deltaPosNED = receiverPos - rngBcnDataDelayed.beacon_posNED;
     402           0 :         deltaPosNED.z -= bcnPosOffset;
     403           0 :         innovRngBcn = deltaPosNED.length() - rngBcnDataDelayed.rng;
     404             : 
     405             :         // update the state
     406           0 :         receiverPos.x -= K_RNG[0] * innovRngBcn;
     407           0 :         receiverPos.y -= K_RNG[1] * innovRngBcn;
     408           0 :         receiverPos.z -= K_RNG[2] * innovRngBcn;
     409           0 :         receiverPos.z = MAX(receiverPos.z, minBcnPosD + 1.2f);
     410             : 
     411             :         // calculate the covariance correction
     412           0 :         for (unsigned i = 0; i<=2; i++) {
     413           0 :             for (unsigned j = 0; j<=2; j++) {
     414           0 :                 KH[i][j] = K_RNG[i] * H_RNG[j];
     415             :             }
     416             :         }
     417           0 :         for (unsigned j = 0; j<=2; j++) {
     418           0 :             for (unsigned i = 0; i<=2; i++) {
     419             :                 ftype res = 0;
     420           0 :                 res += KH[i][0] * receiverPosCov[0][j];
     421           0 :                 res += KH[i][1] * receiverPosCov[1][j];
     422           0 :                 res += KH[i][2] * receiverPosCov[2][j];
     423           0 :                 KHP[i][j] = res;
     424             :             }
     425             :         }
     426             :         // prevent negative variances
     427           0 :         for (uint8_t i= 0; i<=2; i++) {
     428           0 :             if (receiverPosCov[i][i] < 0.0f) {
     429           0 :                 receiverPosCov[i][i] = 0.0f;
     430           0 :                 KHP[i][i] = 0.0f;
     431           0 :             } else if (KHP[i][i] > receiverPosCov[i][i]) {
     432           0 :                 KHP[i][i] = receiverPosCov[i][i];
     433             :             }
     434             :         }
     435             :         // apply the covariance correction
     436           0 :         for (uint8_t i= 0; i<=2; i++) {
     437           0 :             for (uint8_t j= 0; j<=2; j++) {
     438           0 :                 receiverPosCov[i][j] -= KHP[i][j];
     439             :             }
     440             :         }
     441             :         // ensure the covariance matrix is symmetric
     442           0 :         for (uint8_t i=1; i<=2; i++) {
     443           0 :             for (uint8_t j=0; j<=i-1; j++) {
     444           0 :                 ftype temp = 0.5f*(receiverPosCov[i][j] + receiverPosCov[j][i]);
     445           0 :                 receiverPosCov[i][j] = temp;
     446           0 :                 receiverPosCov[j][i] = temp;
     447             :             }
     448             :         }
     449             : 
     450           0 :         if (numBcnMeas >= 100) {
     451             :             // 100 observations is enough for a stable estimate under most conditions
     452             :             // TODO monitor stability of the position estimate
     453           0 :             rngBcnAlignmentCompleted = true;
     454             :         }
     455             :     }
     456             : }
     457             : 
     458             : /*
     459             : Run a single state Kalman filter to estimate the vertical position offset of the range beacon constellation
     460             : Calculate using a high and low hypothesis and select the hypothesis with the lowest innovation sequence
     461             : */
     462           0 : void NavEKF2_core::CalcRangeBeaconPosDownOffset(ftype obsVar, Vector3F &vehiclePosNED, bool aligning)
     463             : {
     464             :     // Handle height offsets between the primary height source and the range beacons by estimating
     465             :     // the beacon systems global vertical position offset using a single state Kalman filter
     466             :     // The estimated offset is used to correct the beacon height when calculating innovations
     467             :     // A high and low estimate is calculated to handle the ambiguity in height associated with beacon positions that are co-planar
     468             :     // The main filter then uses the offset with the smaller innovations
     469             : 
     470             :     ftype innov;    // range measurement innovation (m)
     471             :     ftype innovVar; // range measurement innovation variance (m^2)
     472             :     ftype gain;     // Kalman gain
     473             :     ftype obsDeriv; // derivative of observation relative to state
     474             : 
     475             :     const ftype stateNoiseVar = 0.1f; // State process noise variance
     476             :     const ftype filtAlpha = 0.01f; // LPF constant
     477             :     const ftype innovGateWidth = 5.0f; // width of innovation consistency check gate in std
     478             : 
     479             :     // estimate upper value for offset
     480             : 
     481             :     // calculate observation derivative
     482           0 :     ftype t2 = rngBcnDataDelayed.beacon_posNED.z - vehiclePosNED.z + bcnPosOffsetMax;
     483           0 :     ftype t3 = rngBcnDataDelayed.beacon_posNED.y - vehiclePosNED.y;
     484           0 :     ftype t4 = rngBcnDataDelayed.beacon_posNED.x - vehiclePosNED.x;
     485           0 :     ftype t5 = t2*t2;
     486           0 :     ftype t6 = t3*t3;
     487           0 :     ftype t7 = t4*t4;
     488           0 :     ftype t8 = t5+t6+t7;
     489             :     ftype t9;
     490           0 :     if (t8 > 0.1f) {
     491           0 :         t9 = 1.0f/sqrtF(t8);
     492           0 :         obsDeriv = t2*t9;
     493             : 
     494             :         // Calculate innovation
     495           0 :         innov = sqrtF(t8) - rngBcnDataDelayed.rng;
     496             : 
     497             :         // calculate a filtered innovation magnitude to be used to select between the high or low offset
     498           0 :         OffsetMaxInnovFilt = (1.0f - filtAlpha) * bcnPosOffsetMaxVar + filtAlpha * fabsF(innov);
     499             : 
     500             :         // covariance prediction
     501           0 :         bcnPosOffsetMaxVar += stateNoiseVar;
     502             : 
     503             :         // calculate the innovation variance
     504           0 :         innovVar = obsDeriv * bcnPosOffsetMaxVar * obsDeriv + obsVar;
     505           0 :         innovVar = MAX(innovVar, obsVar);
     506             : 
     507             :         // Reject range innovation spikes using a 5-sigma threshold unless aligning
     508           0 :         if ((sq(innov) < sq(innovGateWidth) * innovVar) || aligning) {
     509             :             // calculate the Kalman gain
     510           0 :             gain = (bcnPosOffsetMaxVar * obsDeriv) / innovVar;
     511             : 
     512             :             // state update
     513           0 :             bcnPosOffsetMax -= innov * gain;
     514             : 
     515             :             // covariance update
     516           0 :             bcnPosOffsetMaxVar -= gain * obsDeriv * bcnPosOffsetMaxVar;
     517           0 :             bcnPosOffsetMaxVar = MAX(bcnPosOffsetMaxVar, 0.0f);
     518             :         }
     519             :     }
     520             : 
     521             :     // estimate lower value for offset
     522             : 
     523             :     // calculate observation derivative
     524           0 :     t2 = rngBcnDataDelayed.beacon_posNED.z - vehiclePosNED.z + bcnPosOffsetMin;
     525           0 :     t5 = t2*t2;
     526           0 :     t8 = t5+t6+t7;
     527           0 :     if (t8 > 0.1f) {
     528           0 :         t9 = 1.0f/sqrtF(t8);
     529           0 :         obsDeriv = t2*t9;
     530             : 
     531             :         // Calculate innovation
     532           0 :         innov = sqrtF(t8) - rngBcnDataDelayed.rng;
     533             : 
     534             :         // calculate a filtered innovation magnitude to be used to select between the high or low offset
     535           0 :         OffsetMinInnovFilt = (1.0f - filtAlpha) * OffsetMinInnovFilt + filtAlpha * fabsF(innov);
     536             : 
     537             :         // covariance prediction
     538           0 :         bcnPosOffsetMinVar += stateNoiseVar;
     539             : 
     540             :         // calculate the innovation variance
     541           0 :         innovVar = obsDeriv * bcnPosOffsetMinVar * obsDeriv + obsVar;
     542           0 :         innovVar = MAX(innovVar, obsVar);
     543             : 
     544             :         // Reject range innovation spikes using a 5-sigma threshold unless aligning
     545           0 :         if ((sq(innov) < sq(innovGateWidth) * innovVar) || aligning) {
     546             :             // calculate the Kalman gain
     547           0 :             gain = (bcnPosOffsetMinVar * obsDeriv) / innovVar;
     548             : 
     549             :             // state update
     550           0 :             bcnPosOffsetMin -= innov * gain;
     551             : 
     552             :             // covariance update
     553           0 :             bcnPosOffsetMinVar -= gain * obsDeriv * bcnPosOffsetMinVar;
     554           0 :             bcnPosOffsetMinVar = MAX(bcnPosOffsetMinVar, 0.0f);
     555             :         }
     556             :     }
     557             : 
     558             :     // calculate the mid vertical position of all beacons
     559           0 :     ftype bcnMidPosD = 0.5f * (minBcnPosD + maxBcnPosD);
     560             : 
     561             :     // ensure the two beacon vertical offset hypothesis place the mid point of the beacons below and above the flight vehicle
     562           0 :     bcnPosOffsetMax = MAX(bcnPosOffsetMax, vehiclePosNED.z - bcnMidPosD + 0.5f);
     563           0 :     bcnPosOffsetMin  = MIN(bcnPosOffsetMin,  vehiclePosNED.z - bcnMidPosD - 0.5f);
     564             : 
     565             :     // calculate the innovation for the main filter using the offset with the smallest innovation history
     566           0 :     if (OffsetMaxInnovFilt > OffsetMinInnovFilt) {
     567           0 :         bcnPosOffset = bcnPosOffsetMin;
     568             :     } else {
     569           0 :         bcnPosOffset = bcnPosOffsetMax;
     570             :     }
     571             : 
     572           0 : }
     573             : 
     574             : #endif  // AP_BEACON_ENABLED

Generated by: LCOV version 1.14