LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduCopter - ekf_check.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 119 143 83.2 %
Date: 2026-09-09 11:02:08 Functions: 6 7 85.7 %

          Line data    Source code
       1             : #include "Copter.h"
       2             : 
       3             : /**
       4             :  *
       5             :  * Detects failures of the ekf or inertial nav system triggers an alert
       6             :  * to the pilot and helps take countermeasures
       7             :  *
       8             :  */
       9             : 
      10             : #ifndef EKF_CHECK_ITERATIONS_MAX
      11             :  # define EKF_CHECK_ITERATIONS_MAX          10      // 1 second (ie. 10 iterations at 10hz) of bad variances signals a failure
      12             : #endif
      13             : 
      14             : #ifndef EKF_CHECK_WARNING_TIME
      15             :  # define EKF_CHECK_WARNING_TIME            (30*1000)   // warning text messages are sent to ground no more than every 30 seconds
      16             : #endif
      17             : 
      18             : ////////////////////////////////////////////////////////////////////////////////
      19             : // EKF_check structure
      20             : ////////////////////////////////////////////////////////////////////////////////
      21             : static struct {
      22             :     uint8_t fail_count;         // number of iterations ekf or dcm have been out of tolerances
      23             :     bool bad_variance;          // true if ekf should be considered untrusted (fail_count has exceeded EKF_CHECK_ITERATIONS_MAX)
      24             :     bool has_ever_passed;       // true if the ekf checks have ever passed
      25             :     uint32_t last_warn_time;    // system time of last warning in milliseconds.  Used to throttle text warnings sent to GCS
      26             : } ekf_check_state;
      27             : 
      28             : // ekf_check - detects if ekf variance are out of tolerance and triggers failsafe
      29             : // should be called at 10hz
      30      759512 : void Copter::ekf_check()
      31             : {
      32             :     // ensure EKF_CHECK_ITERATIONS_MAX is at least 7
      33             :     static_assert(EKF_CHECK_ITERATIONS_MAX >= 7, "EKF_CHECK_ITERATIONS_MAX must be at least 7");
      34             : 
      35             :     // exit immediately if ekf has no origin yet - this assumes the origin can never become unset
      36      759512 :     Location temp_loc;
      37      759512 :     if (!ahrs.get_origin(temp_loc)) {
      38      172047 :         return;
      39             :     }
      40             : 
      41             :     // return immediately if ekf check is disabled
      42      664974 :     if (g.fs_ekf_thresh <= 0.0f) {
      43           0 :         ekf_check_state.fail_count = 0;
      44           0 :         ekf_check_state.bad_variance = false;
      45           0 :         AP_Notify::flags.ekf_bad = ekf_check_state.bad_variance;
      46           0 :         failsafe_ekf_off_event();   // clear failsafe
      47           0 :         return;
      48             :     }
      49             : 
      50             :     // compare compass and velocity variance vs threshold and also check
      51             :     // if we has a position estimate
      52      664974 :     const bool over_threshold = ekf_over_threshold();
      53      664974 :     const bool has_position = ekf_has_relative_position() || ekf_has_absolute_position();
      54      664974 :     const bool checks_passed = !over_threshold && has_position;
      55             : 
      56             :     // return if ekf checks have never passed
      57      664974 :     ekf_check_state.has_ever_passed |= checks_passed;
      58      664974 :     if (!ekf_check_state.has_ever_passed) {
      59       77509 :         return;
      60             :     }
      61             : 
      62             :     // increment or decrement counters and take action
      63      587465 :     if (!checks_passed) {
      64             :         // if variances are not yet flagged as bad
      65        4605 :         if (!ekf_check_state.bad_variance) {
      66             :             // increase counter
      67         466 :             ekf_check_state.fail_count++;
      68         466 :             if (ekf_check_state.fail_count == (EKF_CHECK_ITERATIONS_MAX-2) && over_threshold) {
      69             :                 // we are two iterations away from declaring an EKF failsafe, ask the EKF if we can reset
      70             :                 // yaw to resolve the issue
      71           6 :                 ahrs.request_yaw_reset();
      72             :             }
      73         466 :             if (ekf_check_state.fail_count == (EKF_CHECK_ITERATIONS_MAX-1)) {
      74             :                 // we are just about to declare a EKF failsafe, ask the EKF if we can
      75             :                 // change lanes to resolve the issue
      76          39 :                 ahrs.check_lane_switch();
      77             :             }
      78             :             // if counter above max then trigger failsafe
      79         466 :             if (ekf_check_state.fail_count >= EKF_CHECK_ITERATIONS_MAX) {
      80             :                 // limit count from climbing too high
      81          39 :                 ekf_check_state.fail_count = EKF_CHECK_ITERATIONS_MAX;
      82          39 :                 ekf_check_state.bad_variance = true;
      83          39 :                 LOGGER_WRITE_ERROR(LogErrorSubsystem::EKFCHECK, LogErrorCode::EKFCHECK_BAD_VARIANCE);
      84             :                 // send message to gcs
      85          39 :                 if ((AP_HAL::millis() - ekf_check_state.last_warn_time) > EKF_CHECK_WARNING_TIME) {
      86          32 :                     gcs().send_text(MAV_SEVERITY_CRITICAL,"EKF variance: %s", over_threshold ? "over thresholds" : "position lost");
      87          32 :                     ekf_check_state.last_warn_time = AP_HAL::millis();
      88             :                 }
      89          39 :                 failsafe_ekf_event();
      90             :             }
      91             :         }
      92             :     } else {
      93             :         // reduce counter
      94      582860 :         if (ekf_check_state.fail_count > 0) {
      95         258 :             ekf_check_state.fail_count--;
      96             : 
      97             :             // if variances are flagged as bad and the counter reaches zero then clear flag
      98         258 :             if (ekf_check_state.bad_variance && ekf_check_state.fail_count == 0) {
      99          19 :                 ekf_check_state.bad_variance = false;
     100          19 :                 LOGGER_WRITE_ERROR(LogErrorSubsystem::EKFCHECK, LogErrorCode::EKFCHECK_VARIANCE_CLEARED);
     101             :                 // clear failsafe
     102          19 :                 failsafe_ekf_off_event();
     103             :             }
     104             :         }
     105             :     }
     106             : 
     107             :     // set AP_Notify flags
     108      587465 :     AP_Notify::flags.ekf_bad = ekf_check_state.bad_variance;
     109             : 
     110             :     // To-Do: add ekf variances to extended status
     111             : }
     112             : 
     113             : // ekf_over_threshold - returns true if the ekf's variance are over the tolerance
     114      664974 : bool Copter::ekf_over_threshold()
     115             : {
     116             :     // use EKF to get variance
     117             :     float position_var, vel_var, height_var, tas_variance;
     118      664974 :     Vector3f mag_variance;
     119      664974 :     variances_valid = ahrs.get_variances(vel_var, position_var, height_var, mag_variance, tas_variance);
     120             : 
     121      664974 :     if (!variances_valid) {
     122         131 :         return false;
     123             :     }
     124             : 
     125      664843 :     uint32_t now_us = AP_HAL::micros();
     126      664843 :     float dt = (now_us - last_ekf_check_us) * 1e-6f;
     127             : 
     128             :     // always update filtered values as this serves the vibration check as well
     129      664843 :     position_var = pos_variance_filt.apply(position_var, dt);
     130      664843 :     vel_var = vel_variance_filt.apply(vel_var, dt);
     131             : 
     132      664843 :     last_ekf_check_us = now_us;
     133             : 
     134             :     // return false if disabled
     135      664843 :     if (g.fs_ekf_thresh <= 0.0f) {
     136           0 :         return false;
     137             :     }
     138             : 
     139      664843 :     const float mag_max = fmaxf(fmaxf(mag_variance.x,mag_variance.y),mag_variance.z);
     140             : 
     141             :     // return true if two of compass, velocity and position variances are over the threshold OR velocity variance is twice the threshold
     142      664843 :     uint8_t over_thresh_count = 0;
     143      664843 :     if (mag_max >= g.fs_ekf_thresh) {
     144        1369 :         over_thresh_count++;
     145             :     }
     146             : 
     147      664843 :     bool optflow_healthy = false;
     148             : #if AP_OPTICALFLOW_ENABLED
     149      664843 :     optflow_healthy = optflow.healthy();
     150             : #endif
     151      664843 :     if (!optflow_healthy && (vel_var >= (2.0f * g.fs_ekf_thresh))) {
     152         105 :         over_thresh_count += 2;
     153      664738 :     } else if (vel_var >= g.fs_ekf_thresh) {
     154          84 :         over_thresh_count++;
     155             :     }
     156             : 
     157      664843 :     if ((position_var >= g.fs_ekf_thresh && over_thresh_count >= 1) || over_thresh_count >= 2) {
     158         129 :         return true;
     159             :     }
     160             : 
     161      664714 :     return false;
     162             : }
     163             : 
     164             : 
     165             : // failsafe_ekf_event - perform ekf failsafe
     166          39 : void Copter::failsafe_ekf_event()
     167             : {
     168             :     // EKF failsafe event has occurred
     169          39 :     failsafe.ekf = true;
     170          39 :     LOGGER_WRITE_ERROR(LogErrorSubsystem::FAILSAFE_EKFINAV, LogErrorCode::FAILSAFE_OCCURRED);
     171             : 
     172             :     // if disarmed take no action
     173          39 :     if (!motors->armed()) {
     174          29 :         return;
     175             :     }
     176             : 
     177             :     // set true if ekf failsafe is triggered
     178          10 :     AP_Notify::flags.failsafe_ekf = true;
     179             : 
     180             :     // True if no action should be taken
     181          10 :     const bool report_only = g.fs_ekf_action == FS_EKF_Action::REPORT_ONLY;
     182             : 
     183             :     // sometimes LAND *does* require GPS so ensure we are in non-GPS land
     184          10 :     const bool landing_with_position = landing_with_GPS();
     185          10 :     if (landing_with_position && !report_only) {
     186           0 :         mode_land.do_not_use_GPS();
     187             :     }
     188             : 
     189             :     // does this mode require position?
     190          10 :     const bool no_action_in_current_mode = !copter.flightmode->requires_position() && (g.fs_ekf_action != FS_EKF_Action::LAND_EVEN_STABILIZE);
     191             : 
     192          10 :     if (report_only || landing_with_position || no_action_in_current_mode) {
     193           5 :         gcs().send_text(MAV_SEVERITY_CRITICAL, "EKF Failsafe");
     194           5 :         return;
     195             :     }
     196             : 
     197             :     // take action based on fs_ekf_action parameter
     198           5 :     switch ((FS_EKF_Action)g.fs_ekf_action) {
     199           0 :         case FS_EKF_Action::REPORT_ONLY:
     200             :             // Should have early returned above
     201           0 :             break;
     202           0 :         case FS_EKF_Action::ALTHOLD:
     203             :             // AltHold
     204           0 :             if (failsafe.radio || !set_mode(Mode::Number::ALT_HOLD, ModeReason::EKF_FAILSAFE)) {
     205           0 :                 set_mode_land_with_pause(ModeReason::EKF_FAILSAFE);
     206             :             }
     207           0 :             break;
     208           5 :         case FS_EKF_Action::LAND:
     209             :         case FS_EKF_Action::LAND_EVEN_STABILIZE:
     210             :         default:
     211           5 :             set_mode_land_with_pause(ModeReason::EKF_FAILSAFE);
     212           5 :             break;
     213             :     }
     214             : 
     215           5 :     gcs().send_text(MAV_SEVERITY_CRITICAL, "EKF Failsafe: changed to %s Mode", flightmode->name());
     216             : }
     217             : 
     218             : // failsafe_ekf_off_event - actions to take when EKF failsafe is cleared
     219          19 : void Copter::failsafe_ekf_off_event(void)
     220             : {
     221             :     // return immediately if not in ekf failsafe
     222          19 :     if (!failsafe.ekf) {
     223           0 :         return;
     224             :     }
     225             : 
     226          19 :     failsafe.ekf = false;
     227          19 :     if (AP_Notify::flags.failsafe_ekf) {
     228           7 :         AP_Notify::flags.failsafe_ekf = false;
     229           7 :         gcs().send_text(MAV_SEVERITY_CRITICAL, "EKF Failsafe Cleared");
     230             :     }
     231          19 :     LOGGER_WRITE_ERROR(LogErrorSubsystem::FAILSAFE_EKFINAV, LogErrorCode::FAILSAFE_RESOLVED);
     232             : }
     233             : 
     234             : // re-check if the flight mode requires GPS but EKF failsafe is active
     235             : // this should be called by flight modes that are changing their submode from one that does NOT require a position estimate to one that does
     236           0 : void Copter::failsafe_ekf_recheck()
     237             : {
     238             :     // return immediately if not in ekf failsafe
     239           0 :     if (!failsafe.ekf) {
     240           0 :         return;
     241             :     }
     242             : 
     243             :     // trigger EKF failsafe action
     244           0 :     failsafe_ekf_event();
     245             : }
     246             : 
     247             : // check for ekf yaw reset and adjust target heading, also log position reset
     248    30412448 : void Copter::check_ekf_reset()
     249             : {
     250             :     // check for yaw reset
     251    30412448 :     const uint16_t new_yaw_reset_count = ahrs.get_yaw_reset_count();
     252    30412448 :     if (new_yaw_reset_count != ahrs_yaw_reset_count) {
     253        2087 :         attitude_control->inertial_frame_reset();
     254        2087 :         ahrs_yaw_reset_count = new_yaw_reset_count;
     255             :     }
     256             : 
     257             :     // check for change in primary EKF, reset attitude target and log.  AC_PosControl handles position target adjustment
     258    30412448 :     const auto new_reset_count = ahrs.get_last_attitude_reset_count();
     259    30412448 :     if (new_reset_count != attitude_reset_count) {
     260         952 :         attitude_control->inertial_frame_reset();
     261         952 :         attitude_reset_count = new_reset_count;
     262             :     }
     263    30412448 : }
     264             : 
     265             : // check for high vibrations affecting altitude control
     266      759512 : void Copter::check_vibration()
     267             : {
     268      759512 :     uint32_t now = AP_HAL::millis();
     269             : 
     270             :     // assume checks will succeed
     271      759512 :     bool innovation_checks_valid = true;
     272             : 
     273             :     // check if vertical velocity and position innovations are positive (NKF3.IVD & NKF3.IPD are both positive)
     274      759512 :     Vector3f vel_innovation;
     275      759512 :     Vector3f pos_innovation;
     276      759512 :     Vector3f mag_innovation;
     277             :     float tas_innovation;
     278             :     float yaw_innovation;
     279      759512 :     if (!ahrs.get_innovations(vel_innovation, pos_innovation, mag_innovation, tas_innovation, yaw_innovation)) {
     280        9819 :         innovation_checks_valid = false;
     281             :     }
     282      759512 :     const bool innov_velD_posD_positive = is_positive(vel_innovation.z) && is_positive(pos_innovation.z);
     283             : 
     284             :     // check if vertical velocity variance is at least 1 (NK4.SV >= 1.0)
     285             :     // filtered variances are updated in ekf_check() which runs at the same rate (10Hz) as this check
     286      759512 :     if (!variances_valid) {
     287       94669 :         innovation_checks_valid = false;
     288             :     }
     289      759512 :     const bool is_vibration_affected = ahrs.is_vibration_affected();
     290      759512 :     const bool bad_vibe_detected = (innovation_checks_valid && innov_velD_posD_positive && (vel_variance_filt.get() > 1.0f)) || is_vibration_affected;
     291      759512 :     const bool do_bad_vibe_actions = (g2.fs_vibe_enabled == 1) && bad_vibe_detected && motors->armed() && !flightmode->has_manual_throttle();
     292             : 
     293      759512 :     if (!vibration_check.high_vibes) {
     294             :         // initialise timers
     295      759219 :         if (!do_bad_vibe_actions) {
     296      759208 :             vibration_check.start_ms = now;
     297             :         }
     298             :         // check if failure has persisted for at least 1 second
     299      759219 :         if (now - vibration_check.start_ms > 1000) {
     300             :             // switch position controller to use resistant gains
     301           1 :             vibration_check.clear_ms = 0;
     302           1 :             vibration_check.high_vibes = true;
     303           1 :             pos_control->set_vibe_comp(true);
     304           1 :             LOGGER_WRITE_ERROR(LogErrorSubsystem::FAILSAFE_VIBE, LogErrorCode::FAILSAFE_OCCURRED);
     305           1 :             gcs().send_text(MAV_SEVERITY_CRITICAL, "Vibration compensation ON");
     306             :         }
     307             :     } else {
     308             :         // initialise timer
     309         293 :         if (do_bad_vibe_actions) {
     310         217 :             vibration_check.clear_ms = now;
     311             :         }
     312             :         // turn off vibration compensation after 15 seconds
     313         293 :         if (now - vibration_check.clear_ms > 15000) {
     314             :             // restore position controller gains, reset timers and update user
     315           0 :             vibration_check.start_ms = 0;
     316           0 :             vibration_check.high_vibes = false;
     317           0 :             pos_control->set_vibe_comp(false);
     318           0 :             vibration_check.clear_ms = 0;
     319           0 :             LOGGER_WRITE_ERROR(LogErrorSubsystem::FAILSAFE_VIBE, LogErrorCode::FAILSAFE_RESOLVED);
     320           0 :             gcs().send_text(MAV_SEVERITY_CRITICAL, "Vibration compensation OFF");
     321             :         }
     322             :     }
     323             : 
     324      759512 :     return;
     325             : }

Generated by: LCOV version 1.14