LCOV - code coverage report
Current view: top level - AC_PrecLand - AC_PrecLand_StateMachine.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 36 117 30.8 %
Date: 2026-09-09 11:02:08 Functions: 4 6 66.7 %

          Line data    Source code
       1             : #include "AC_PrecLand_config.h"
       2             : 
       3             : #if AC_PRECLAND_ENABLED
       4             : 
       5             : #include <AC_PrecLand/AC_PrecLand.h>
       6             : #include "AC_PrecLand_StateMachine.h"
       7             : #include <AP_AHRS/AP_AHRS.h>
       8             : #include <GCS_MAVLink/GCS.h>
       9             : 
      10             : static const float MAX_POS_ERROR_M = 0.75f;  // Maximum position error for retry locations
      11             : static const uint32_t FAILSAFE_INIT_TIMEOUT_MS = 7000;   // Timeout in ms before failsafe measures are started. During this period vehicle is completely stopped to give user the time to take over
      12             : static const float RETRY_OFFSET_ALT_M = 1.5f;  // This gets added to the altitude of the retry location
      13             : 
      14             : // Initialize the state machine. This is called every time vehicle switches mode
      15        1516 : void AC_PrecLand_StateMachine::init()
      16             : {
      17        1516 :     AC_PrecLand *_precland = AP::ac_precland();
      18        1516 :     if (_precland == nullptr) {
      19             :         // precland not enabled
      20           0 :         return;
      21             :     }
      22             : 
      23        1516 :     if (!_precland->enabled()) {
      24             :         // precland is not enabled, prec land state machine methods should not be called!
      25        1509 :         return;
      26             :     }
      27             :     // init is only called ONCE per mode change. So in a particular mode we can retry only a finite times.
      28             :     // The counter will be reset if the statemachine is called from a different mode
      29           7 :     _retry_count = 0;
      30             :     // reset every other statemachine
      31           7 :     reset_failed_landing_statemachine();
      32             : }
      33             : 
      34             : // Reset the landing statemachines. This needs to be called every time the landing target is back in sight.
      35             : // So that if the landing target goes out of sight again, we can start the failed landing procedure back from the beginning stage
      36       46704 : void AC_PrecLand_StateMachine::reset_failed_landing_statemachine()
      37             : {
      38       46704 :     landing_target_lost_action = TargetLostAction::INIT;
      39       46704 :     _retry_state = RetryLanding::INIT;
      40       46704 :     failsafe_initialized = false;
      41       46704 : }
      42             : 
      43             : // Run Prec Land State Machine. During Prec Landing, we might encounter four scenarios:
      44             : // 1. We had the target in sight, but have lost it now. 2. We never had the target in sight and user wants to land.
      45             : // 3. We have the target in sight and can continue landing. 4. The sensor is out of range
      46             : // This method deals with all of these scenarios
      47             : // Returns the action needed to be done by the vehicle.
      48             : // Parameters: Vector3f "retry_pos_m" is filled with the required location if we need to retry landing.
      49       63857 : AC_PrecLand_StateMachine::Status AC_PrecLand_StateMachine::update(Vector3p &retry_pos_m)
      50             : {
      51             : 
      52             :     // grab the current status of Landing Target
      53       63857 :     AC_PrecLand *_precland = AP::ac_precland();
      54       63857 :     if (_precland == nullptr) {
      55             :         // should never happen
      56           0 :         return Status::ERROR;
      57             :     }
      58             : 
      59       63857 :     if (!_precland->enabled()) {
      60             :         // precland is not enabled, prec land state machine should not be called!
      61           0 :         return Status::ERROR;
      62             :     }
      63             : 
      64       63857 :     AC_PrecLand::TargetState precland_target_state =  _precland->get_target_state();
      65             : 
      66       63857 :     switch (precland_target_state) {
      67           0 :     case AC_PrecLand::TargetState::TARGET_RECENTLY_LOST:
      68             :         // we have lost the target but had it in sight at least once recently
      69             :         // action will depend on what user wants
      70           0 :         return get_target_lost_actions(retry_pos_m);
      71             : 
      72       17160 :     case AC_PrecLand::TargetState::TARGET_NEVER_SEEN:
      73             :         // we have no clue where we are supposed to be landing
      74             :         // let user decide how strict our failsafe actions need to be
      75       17160 :         return Status::FAILSAFE;
      76             : 
      77       46697 :     case AC_PrecLand::TargetState::TARGET_OUT_OF_RANGE:
      78             :         // The target isn't in sight, but we can't run any fail safe measures or do landing retry
      79             :         // Therefore just descend for now, and check again later if retry is allowed
      80             :     case AC_PrecLand::TargetState::TARGET_FOUND:
      81             :         // no action required, target is in sight
      82       46697 :         reset_failed_landing_statemachine();
      83       46697 :         return Status::DESCEND;
      84             :     }
      85             : 
      86             :     // should never reach here, all values are handled above
      87           0 :     return Status::ERROR;
      88             : }
      89             : 
      90             : 
      91             : // Target is lost (i.e we had it in sight some time back), this method helps decide on what needs to be done next
      92             : // The chosen action depends on user set landing strictness and will be returned by this function
      93             : // Parameters: Vector3f "retry_pos_m" is filled with the required location if we need to retry landing.
      94           0 : AC_PrecLand_StateMachine::Status AC_PrecLand_StateMachine::get_target_lost_actions(Vector3p &retry_pos_m)
      95             : {
      96           0 :     AC_PrecLand *_precland = AP::ac_precland();
      97           0 :     if (_precland == nullptr) {
      98             :         // should never happen
      99           0 :         return Status::ERROR;
     100             :     }
     101             : 
     102           0 :     switch (landing_target_lost_action) {
     103           0 :     case TargetLostAction::INIT: {
     104             :         // figure out how strict the user is with the landing
     105           0 :         const RetryStrictness strictness =_precland->get_retry_strictness();
     106             :         switch (strictness) {
     107           0 :             case RetryStrictness::NORMAL:
     108             :             case RetryStrictness::VERY_STRICT:
     109             :                 // We eventually want to retry landing, but lets descend for some time and hope the target gets in sight
     110             :                 // If not, we will retry landing
     111           0 :                 landing_target_lost_action = TargetLostAction::DESCEND;
     112           0 :                 break;
     113           0 :             case RetryStrictness::NOT_STRICT:
     114             :                 // User just wants to land, prec land isn't a priority
     115           0 :                 landing_target_lost_action = TargetLostAction::LAND_VERTICALLY;
     116           0 :                 break;
     117             :         }
     118             :         // at this stage we will be descending no matter what
     119             :         // so no special action required
     120           0 :         return Status::DESCEND;
     121             :     }
     122             : 
     123           0 :     case TargetLostAction::DESCEND:
     124           0 :         if (AP_HAL::millis() - _precland->get_last_valid_target_ms() >=_precland->get_min_retry_time_sec() * 1000) {
     125             :             // we have descended for some time and the target still isn't in sight
     126             :             // lets retry
     127           0 :             landing_target_lost_action = TargetLostAction::RETRY_LANDING;
     128           0 :             _retry_state = RetryLanding::INIT;
     129             :         }
     130             :         // still descending, no other action
     131           0 :         return Status::DESCEND;
     132             : 
     133           0 :     case TargetLostAction::RETRY_LANDING:
     134             :         // retry the landing by going to another position
     135           0 :         return retry_landing(retry_pos_m);
     136             : 
     137           0 :     case TargetLostAction::LAND_VERTICALLY:
     138             :         // Just land vertically
     139             :         // we will not be retrying to any location here on, so return false
     140           0 :         return Status::DESCEND;
     141             :     }
     142             : 
     143             :     // should never reach here, all cases are handled above
     144           0 :     return Status::ERROR;
     145             : }
     146             : 
     147             : // Retry landing based on a previously known location of the landing target
     148             : // Returns the action that should be taken by the vehicle
     149             : // Vector3f "retry_pos_m" is filled with the required location.
     150           0 : AC_PrecLand_StateMachine::Status AC_PrecLand_StateMachine::retry_landing(Vector3p &retry_pos_m)
     151             : {
     152           0 :     AC_PrecLand *_precland = AP::ac_precland();
     153           0 :     if (_precland == nullptr) {
     154             :         // should never happen
     155           0 :         return Status::ERROR;
     156             :     }
     157             : 
     158           0 :     if (_precland->get_max_retry_allowed() == 0) {
     159             :         // user does not want retry
     160           0 :         return Status::FAILSAFE;
     161             :     }
     162             : 
     163           0 :     if (_retry_count > _precland->get_max_retry_allowed()) {
     164             :         // we have exhausted the amount of times vehicle was allowed to retry landing
     165             :         // do failsafe measure so the vehicle isn't stuck in a constant loop
     166           0 :         return Status::FAILSAFE;
     167             :     }
     168             : 
     169             :     // get the retry position. This depends on what retry behavior has been set by user
     170           0 :     Vector3p go_to_pos;
     171           0 :     const RetryAction retry_action = _precland->get_retry_behaviour();
     172           0 :     if (retry_action == RetryAction::GO_TO_TARGET_LOC) {
     173           0 :         _precland->get_last_detected_landing_pos_NED_m(go_to_pos);
     174           0 :     } else if (retry_action == RetryAction::GO_TO_LAST_LOC) {
     175           0 :         _precland->get_last_vehicle_pos_when_target_detected_NED_m(go_to_pos);
     176             :     }
     177             : 
     178             :     // add a little bit offset so the vehicle climbs slightly higher than where it was
     179             :     // remember this is "D" frame and in meters's
     180           0 :     go_to_pos.z -= RETRY_OFFSET_ALT_M;
     181             : 
     182           0 :     switch (_retry_state) {
     183           0 :     case RetryLanding::INIT:
     184             :         // Init the Retry
     185           0 :         _retry_count ++;
     186           0 :         _retry_state = RetryLanding::IN_PROGRESS;
     187             :         // inform the user what we are doing
     188           0 :         if (_retry_count <= _precland->get_max_retry_allowed()) {
     189           0 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "PrecLand: Retrying");
     190             :         }
     191           0 :         retry_pos_m = go_to_pos;
     192           0 :         return Status::RETRYING;
     193             : 
     194           0 :     case RetryLanding::IN_PROGRESS: {
     195             :         // continue converging towards the target till we are close by
     196           0 :         retry_pos_m = go_to_pos;
     197           0 :         Vector3p pos;
     198           0 :         if (!AP::ahrs().get_relative_position_NED_origin(pos)) {
     199           0 :             return Status::ERROR;
     200             :         }
     201           0 :         const float dist_to_target = (go_to_pos-pos).length();
     202           0 :         if ((dist_to_target < MAX_POS_ERROR_M)) {
     203             :             // we have approx reached landing location previously detected
     204           0 :             _retry_state = RetryLanding::DESCEND;
     205             :         }
     206           0 :         return Status::RETRYING;
     207             :     }
     208             : 
     209           0 :     case RetryLanding::DESCEND: {
     210             :         // descend a little bit before completing the retry
     211             :         // This will descend to the original height of where landing target was first detected
     212           0 :         Vector3p pos;
     213           0 :         if (!AP::ahrs().get_relative_position_NED_origin(pos)) {
     214           0 :             return Status::ERROR;
     215             :         }
     216             :         // z_target is in "D" frame
     217           0 :         const float z_target = go_to_pos.z + RETRY_OFFSET_ALT_M;
     218           0 :         retry_pos_m = Vector3p{pos.x, pos.y, z_target};
     219           0 :         if (fabsf(pos.z - retry_pos_m.z) < MAX_POS_ERROR_M) {
     220             :             // we have descended to the original height where we started the climb from
     221           0 :             _retry_state = RetryLanding::COMPLETE;
     222           0 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "PrecLand: Retry Completed");
     223             :         }
     224           0 :         return Status::RETRYING;
     225             :     }
     226             : 
     227           0 :     case RetryLanding::COMPLETE:
     228             :         // Vehicle has completed a retry, and most likely the landing location still isn't sight
     229             :         // we have no choice but to force a failsafe action
     230           0 :         return Status::FAILSAFE;
     231             :     }
     232             : 
     233             :     // should never reach here
     234           0 :     return Status::ERROR;
     235             : }
     236             : 
     237             : // This is only called when the current status of the state machine returns "failsafe" and will return the action that the vehicle should do
     238             : // At the moment this method only allows you to stop in air permanently, or land vertically
     239             : // Failsafe will only trigger as a last resort
     240       17160 : AC_PrecLand_StateMachine::FailSafeAction AC_PrecLand_StateMachine::get_failsafe_actions()
     241             : {
     242       17160 :     AC_PrecLand *_precland = AP::ac_precland();
     243       17160 :     if (_precland == nullptr) {
     244             :         // should never happen, just descend
     245           0 :         return FailSafeAction::DESCEND;
     246             :     }
     247             : 
     248       17160 :     if (!failsafe_initialized) {
     249             :         // start the timer
     250           2 :         failsafe_start_ms = AP_HAL::millis();
     251           2 :         failsafe_initialized = true;
     252           2 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "PrecLand: Failsafe Measures");
     253             :     }
     254             : 
     255             :     // Depending on the strictness we will either land vertically, wait for some time and then land vertically, not land at all
     256       17160 :     const RetryStrictness strictness= _precland->get_retry_strictness();
     257       17160 :     switch (strictness) {
     258           0 :     case RetryStrictness::VERY_STRICT:
     259             :         // user does not want to land on anything but the target
     260             :         // stop landing (hover)
     261           0 :         return FailSafeAction::HOLD_POS;
     262             : 
     263       17160 :     case RetryStrictness::NORMAL:
     264       17160 :         if (AP_HAL::millis() - failsafe_start_ms < FAILSAFE_INIT_TIMEOUT_MS) {
     265             :             // stop the vehicle for at least a few seconds before descending
     266             :             // this might give user the chance to take over
     267             :             // we do not want to be too linent in landing vertically because of the strictness set by the user
     268        5600 :             return FailSafeAction::HOLD_POS;
     269             :         }
     270             :         // land the vehicle vertically
     271       11560 :         return FailSafeAction::DESCEND;
     272             : 
     273           0 :     case RetryStrictness::NOT_STRICT:
     274             :         // User wants to prioritize landing over staying in the air
     275           0 :         return FailSafeAction::DESCEND;
     276             :     }
     277             : 
     278             :     // should never reach here
     279           0 :     return FailSafeAction::DESCEND;
     280             : }
     281             : 
     282             : #endif // AC_PRECLAND_ENABLED

Generated by: LCOV version 1.14