LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - navigation.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 219 228 96.1 %
Date: 2026-09-09 11:02:08 Functions: 11 11 100.0 %

          Line data    Source code
       1             : #include "Plane.h"
       2             : 
       3             : /*
       4             :   reset the total loiter angle
       5             :  */
       6        4118 : void Plane::loiter_angle_reset(void)
       7             : {
       8        4118 :     loiter.sum_cd = 0;
       9        4118 :     loiter.total_cd = 0;
      10        4118 :     loiter.reached_target_alt = false;
      11        4118 :     loiter.unable_to_achieve_target_alt = false;
      12        4118 : }
      13             : 
      14             : /*
      15             :   update the total angle we have covered in a loiter. Used to support
      16             :   commands to do N circles of loiter
      17             :  */
      18      511367 : void Plane::loiter_angle_update(void)
      19             : {
      20             :     static const int32_t lap_check_interval_cd = 3*36000;
      21             : 
      22      511367 :     const int32_t target_bearing_cd = nav_controller->target_bearing_cd();
      23             :     int32_t loiter_delta_cd;
      24      511367 :     const bool reached_target = reached_loiter_target();
      25             : 
      26      511367 :     if (loiter.sum_cd == 0 && !reached_target) {
      27             :         // we don't start summing until we are doing the real loiter
      28      373248 :         loiter_delta_cd = 0;
      29      138119 :     } else if (loiter.sum_cd == 0) {
      30             :         // use 1 cd for initial delta
      31       18219 :         loiter_delta_cd = 1;
      32       18219 :         loiter.start_lap_alt_cm = current_loc.alt;
      33       18219 :         loiter.next_sum_lap_cd = lap_check_interval_cd;
      34             :     } else {
      35      119900 :         loiter_delta_cd = target_bearing_cd - loiter.old_target_bearing_cd;
      36             :     }
      37             : 
      38      511367 :     loiter.old_target_bearing_cd = target_bearing_cd;
      39      511367 :     loiter_delta_cd = wrap_180_cd(loiter_delta_cd);
      40      511367 :     loiter.sum_cd += loiter_delta_cd * loiter.direction;
      41             : 
      42      511367 :     bool reached_target_alt = false;
      43             : 
      44      511367 :     if (reached_target) {
      45             :         // once we reach the position target we start checking the
      46             :         // altitude target
      47      107708 :         bool terrain_status_ok = false;
      48             : #if AP_TERRAIN_AVAILABLE
      49             :         /*
      50             :           if doing terrain following then we check against terrain
      51             :           target, fetch the terrain information
      52             :         */
      53      107708 :         float altitude_agl = 0;
      54      107708 :         if (target_altitude.terrain_following) {
      55       24260 :             if (terrain.status() == AP_Terrain::TerrainStatusOK &&
      56       12130 :                 terrain.height_above_terrain(altitude_agl, true)) {
      57       12130 :                 terrain_status_ok = true;
      58             :             }
      59             :         }
      60      119838 :         if (terrain_status_ok &&
      61       12130 :             fabsF(altitude_agl - target_altitude.terrain_alt_cm*0.01) < 5) {
      62        9503 :             reached_target_alt = true;
      63             :         } else
      64             : #endif
      65       98205 :         if (!terrain_status_ok && labs(current_loc.alt - target_altitude.amsl_cm) < 500) {
      66       48906 :             reached_target_alt = true;
      67             :         }
      68             :     }
      69             : 
      70      511367 :     if (reached_target_alt) {
      71       58409 :         loiter.reached_target_alt = true;
      72       58409 :         loiter.unable_to_achieve_target_alt = false;
      73       58409 :         loiter.next_sum_lap_cd = loiter.sum_cd + lap_check_interval_cd;
      74             : 
      75      452958 :     } else if (!loiter.reached_target_alt && labs(loiter.sum_cd) >= loiter.next_sum_lap_cd) {
      76             :         // check every few laps for scenario where up/downward inhibit you from loitering up/down for too long
      77         351 :         loiter.unable_to_achieve_target_alt = labs(current_loc.alt - loiter.start_lap_alt_cm) < 500;
      78         351 :         loiter.start_lap_alt_cm = current_loc.alt;
      79         351 :         loiter.next_sum_lap_cd += lap_check_interval_cd;
      80             :     }
      81      511367 : }
      82             : 
      83             : //****************************************************************
      84             : // Function that will calculate the desired direction to fly and distance
      85             : //****************************************************************
      86      547097 : void Plane::navigate()
      87             : {
      88             :     // do not navigate with corrupt data
      89             :     // ---------------------------------
      90      547097 :     if (!have_position) {
      91       31931 :         return;
      92             :     }
      93             : 
      94      515166 :     if (next_WP_loc.lat == 0 && next_WP_loc.lng == 0) {
      95        3799 :         return;
      96             :     }
      97             : 
      98      511367 :     check_home_alt_change();
      99             : 
     100             :     // waypoint distance from plane
     101             :     // ----------------------------
     102      511367 :     auto_state.wp_distance = current_loc.get_distance(next_WP_loc);
     103      511367 :     auto_state.wp_proportion = current_loc.line_path_proportion(prev_WP_loc, next_WP_loc);
     104      511367 :     TECS_controller.set_path_proportion(auto_state.wp_proportion);
     105             : 
     106             :     // update total loiter angle
     107      511367 :     loiter_angle_update();
     108             : 
     109             :     // control mode specific updates to navigation demands
     110             :     // ---------------------------------------------------
     111      511367 :     control_mode->navigate();
     112             : }
     113             : 
     114             : // method intended for use in calc_airspeed_errors only
     115      205761 : float Plane::mode_auto_target_airspeed_cm()
     116             : {
     117             : #if HAL_QUADPLANE_ENABLED
     118      208357 :     if (quadplane.landing_with_fixed_wing_spiral_approach() &&
     119        2596 :         ((vtol_approach_s.approach_stage == VTOLApproach::Stage::APPROACH_LINE) ||
     120        2540 :          (vtol_approach_s.approach_stage == VTOLApproach::Stage::VTOL_LANDING))) {
     121        1624 :         const float land_airspeed = TECS_controller.get_land_airspeed();
     122        1624 :         if (is_positive(land_airspeed)) {
     123           0 :             return land_airspeed * 100;
     124             :         }
     125             :         // fallover to normal airspeed
     126        1624 :         return aparm.airspeed_cruise*100;
     127             :     }
     128      204137 :     if (quadplane.in_vtol_land_approach()) {
     129        5900 :         return quadplane.get_land_airspeed_ms() * 100;
     130             :     }
     131             : #endif
     132             : 
     133             :     // normal AUTO mode and new_airspeed variable was set by
     134             :     // DO_CHANGE_SPEED command while in AUTO mode
     135      198237 :     if (new_airspeed_cm > 0) {
     136       17023 :         return new_airspeed_cm;
     137             :     }
     138             : 
     139             :     // fallover to normal airspeed
     140      181214 :     return aparm.airspeed_cruise*100;
     141             : }
     142             : 
     143      547097 : void Plane::calc_airspeed_errors()
     144             : {
     145             :     // Get the airspeed_estimate, update smoothed airspeed estimate
     146             :     // NOTE:  we use the airspeed estimate function not direct sensor
     147             :     //        as TECS may be using synthetic airspeed
     148      547097 :     float airspeed_measured = 0.1;
     149      547097 :     if (ahrs.airspeed_EAS(airspeed_measured)) {
     150      542890 :         smoothed_airspeed = MAX(0.1, smoothed_airspeed * 0.8f + airspeed_measured * 0.2f);
     151             :     }
     152             : 
     153             :     // low pass filter speed scaler, with 1Hz cutoff, at 10Hz
     154      547097 :     const float speed_scaler = calc_speed_scaler();
     155      547097 :     const float cutoff_Hz = 2.0;
     156      547097 :     const float dt = 0.1;
     157      547097 :     surface_speed_scaler += calc_lowpass_alpha_dt(dt, cutoff_Hz) * (speed_scaler - surface_speed_scaler);
     158             : 
     159             : 
     160             :     // FBW_B/cruise airspeed target
     161      547097 :     if (!failsafe.rc_failsafe && (control_mode == &mode_fbwb || control_mode == &mode_cruise)) {
     162       13043 :         if (flight_option_enabled(FlightOptions::CRUISE_TRIM_AIRSPEED)) {
     163           0 :             target_airspeed_cm = aparm.airspeed_cruise*100;
     164       13043 :         } else if (flight_option_enabled(FlightOptions::CRUISE_TRIM_THROTTLE)) {
     165        1855 :             float control_min = 0.0f;
     166        1855 :             float control_mid = 0.0f;
     167        1855 :             const float control_max = channel_throttle->get_range();
     168        1855 :             const float control_in = get_throttle_input();
     169        1855 :             switch (channel_throttle->get_type()) {
     170           0 :             case RC_Channel::ControlType::ANGLE:
     171           0 :                     control_min = -control_max;
     172           0 :                     break;
     173        1855 :             case RC_Channel::ControlType::RANGE:
     174        1855 :                     control_mid = channel_throttle->get_control_mid();
     175        1855 :                     break;
     176             :             }
     177        1855 :             if (control_in <= control_mid) {
     178        1855 :                 target_airspeed_cm = linear_interpolate(aparm.airspeed_min * 100, aparm.airspeed_cruise*100,
     179             :                                                         control_in,
     180             :                                                         control_min, control_mid);
     181             :             } else {
     182           0 :                 target_airspeed_cm = linear_interpolate(aparm.airspeed_cruise*100, aparm.airspeed_max * 100,
     183             :                                                         control_in,
     184             :                                                         control_mid, control_max);
     185             :             }
     186             :         } else {
     187       11188 :             target_airspeed_cm = ((int32_t)(aparm.airspeed_max - aparm.airspeed_min) *
     188       11188 :                                   get_throttle_input()) + ((int32_t)aparm.airspeed_min * 100);
     189             :         }
     190             : #if AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED
     191      547097 :     } else if (control_mode == &mode_guided && is_positive(guided_state.target_airspeed_cm)) { // if offboard guided speed change cmd not set, then this section is skipped
     192             :         // offboard airspeed demanded
     193         396 :         uint32_t now = AP_HAL::millis();
     194         396 :         float delta = 1e-3f * (now - guided_state.target_airspeed_time_ms);
     195         396 :         guided_state.target_airspeed_time_ms = now;
     196         396 :         float delta_amt = 100 * delta * guided_state.target_airspeed_accel;
     197         396 :         target_airspeed_cm += delta_amt;
     198             : 
     199             :         //target_airspeed_cm recalculated then clamped to between MIN airspeed and MAX airspeed by constrain_float
     200         396 :         if (is_positive(guided_state.target_airspeed_accel)) {
     201         194 :             target_airspeed_cm = constrain_float(MIN(guided_state.target_airspeed_cm, target_airspeed_cm), aparm.airspeed_min *100, aparm.airspeed_max *100);
     202             :         } else {
     203         202 :             target_airspeed_cm = constrain_float(MAX(guided_state.target_airspeed_cm, target_airspeed_cm), aparm.airspeed_min *100, aparm.airspeed_max *100);
     204             :         }
     205             : 
     206             : #endif // AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED
     207             : 
     208             : #if HAL_SOARING_ENABLED
     209      533658 :     } else if (g2.soaring_controller.is_active() && g2.soaring_controller.get_throttle_suppressed()) {
     210        8182 :         if (control_mode == &mode_thermal) {
     211        2724 :             float arspd = g2.soaring_controller.get_thermalling_target_airspeed();
     212             : 
     213        2724 :             if (arspd > 0) {
     214           0 :                 target_airspeed_cm = arspd * 100;
     215             :             } else {
     216        2724 :                 target_airspeed_cm = aparm.airspeed_cruise*100;
     217             :             }
     218        5458 :         } else if (control_mode == &mode_auto) {
     219        5458 :             float arspd = g2.soaring_controller.get_cruising_target_airspeed();
     220             : 
     221        5458 :             if (arspd > 0) {
     222         623 :                 target_airspeed_cm = arspd * 100;
     223             :             } else {
     224        4835 :                 target_airspeed_cm = aparm.airspeed_cruise*100;
     225             :             }
     226             :         }
     227             : #endif
     228             : 
     229      525476 :     } else if (flight_stage == AP_FixedWing::FlightStage::LAND) {
     230             :         // Landing airspeed target
     231       36337 :         target_airspeed_cm = landing.get_target_airspeed_ms() * 100.0f;  // m/s -> cm/s
     232      489139 :     } else if (control_mode == &mode_guided && new_airspeed_cm > 0) { //DO_CHANGE_SPEED overrides onboard guided speed commands, user would have re-enter guided mode to revert
     233         792 :                        target_airspeed_cm = new_airspeed_cm;
     234      488347 :     } else if (control_mode == &mode_auto) {
     235      205761 :         target_airspeed_cm = mode_auto_target_airspeed_cm();
     236             : #if HAL_QUADPLANE_ENABLED
     237      282586 :     } else if (control_mode == &mode_qrtl && quadplane.in_vtol_land_approach()) {
     238        6406 :         target_airspeed_cm = quadplane.get_land_airspeed_ms() * 100;
     239             : #endif
     240             :     } else {
     241             :         // Normal airspeed target for all other cases
     242      276180 :         target_airspeed_cm = aparm.airspeed_cruise*100;
     243             :     }
     244             : 
     245             :     // Set target to current airspeed + ground speed undershoot,
     246             :     // but only when this is faster than the target airspeed commanded
     247             :     // above.
     248      547097 :     if (control_mode->does_auto_throttle() &&
     249      547965 :         groundspeed_undershoot_is_valid &&
     250         868 :         control_mode != &mode_circle) {
     251             :         /*
     252             :           calculate how much extra airspeed we need to target to
     253             :           achieve the desired ground speed in MIN_GROUNDSPEED
     254             : 
     255             :           we quantise the additional airspeed and apply a hysteresis
     256             :           in order to avoid triggering an oscillation in TECS
     257             :          */
     258         868 :         float target_airspeed = target_airspeed_cm*0.01;
     259         868 :         float EAS_undershoot = (groundspeed_undershoot*0.01) / ahrs.get_EAS2TAS();
     260         868 :         float min_gnd_target_airspeed = airspeed_measured + EAS_undershoot;
     261         868 :         float airspeed_target_offset = min_gnd_target_airspeed > target_airspeed? (min_gnd_target_airspeed - target_airspeed) : 0;
     262             : 
     263             :         // round up to nearest m/s
     264         868 :         airspeed_target_offset = int(airspeed_target_offset + 0.5);
     265             : 
     266             :         // apply some hysteresis
     267         868 :         if (airspeed_target_offset < last_groundspeed_undershoot_offset &&
     268         420 :             last_groundspeed_undershoot_offset - airspeed_target_offset < 1.2) {
     269         389 :             airspeed_target_offset = last_groundspeed_undershoot_offset;
     270             :         }
     271         868 :         last_groundspeed_undershoot_offset = airspeed_target_offset;
     272             : 
     273         868 :         target_airspeed_cm += airspeed_target_offset * 100;
     274             :     }
     275             : 
     276             :     // when using the special GUIDED mode features for slew control, don't allow airspeed nudging as it doesn't play nicely.
     277             : #if AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED
     278      547097 :     if (control_mode == &mode_guided && is_positive(guided_state.target_airspeed_cm) && (airspeed_nudge_cm != 0)) {
     279          84 :         airspeed_nudge_cm = 0; //airspeed_nudge_cm forced to zero
     280             :     }
     281             : #endif
     282             : 
     283             :     // Bump up the target airspeed based on throttle nudging
     284      547097 :     if (control_mode->allows_throttle_nudging() && airspeed_nudge_cm > 0) {
     285       31297 :         target_airspeed_cm += airspeed_nudge_cm;
     286             :     }
     287             : 
     288      547097 :     float airspeed_lower_bound = is_positive(aparm.airspeed_stall)
     289     1094194 :                                      ? aparm.airspeed_stall
     290      547097 :                                      : aparm.airspeed_min;
     291             : 
     292             :     // Apply airspeed limit
     293      547097 :     target_airspeed_cm = constrain_int32(target_airspeed_cm, airspeed_lower_bound*100, aparm.airspeed_max*100);
     294             : 
     295             :     // use the TECS view of the target airspeed for reporting, to take
     296             :     // account of the landing speed
     297      547097 :     airspeed_error = TECS_controller.get_target_airspeed() - airspeed_measured;
     298      547097 : }
     299             : 
     300      547097 : void Plane::calc_gndspeed_undershoot()
     301             : {
     302             :     // Use the component of ground speed in the forward direction
     303             :     // This prevents flyaway if wind takes plane backwards
     304      547097 :     Vector3f velNED;
     305      547097 :     if (ahrs.have_inertial_nav() && ahrs.get_velocity_NED(velNED)) {
     306      471548 :         const Matrix3f &rotMat = ahrs.get_rotation_body_to_ned();
     307      471548 :         Vector2f yawVect = Vector2f(rotMat.a.x,rotMat.b.x);
     308      471548 :         if (!yawVect.is_zero()) {
     309      471548 :             yawVect.normalize();
     310      471548 :             float gndSpdFwd = yawVect * velNED.xy();
     311      471548 :             groundspeed_undershoot_is_valid = aparm.min_groundspeed > 0;
     312      471548 :             groundspeed_undershoot = groundspeed_undershoot_is_valid ? (aparm.min_groundspeed*100 - gndSpdFwd*100) : 0;
     313             :         }
     314             :     } else {
     315       75549 :         groundspeed_undershoot_is_valid = false;
     316       75549 :         groundspeed_undershoot = 0;
     317             :     }
     318      547097 : }
     319             : 
     320             : // method intended to be used by update_loiter
     321      108655 : void Plane::update_loiter_update_nav(uint16_t radius)
     322             : {
     323             : #if HAL_QUADPLANE_ENABLED
     324      186822 :     if (loiter.start_time_ms != 0 &&
     325       78167 :         quadplane.guided_mode_enabled()) {
     326        8780 :         if (!auto_state.vtol_loiter) {
     327          28 :             auto_state.vtol_loiter = true;
     328             :             // reset loiter start time, so we don't consider the point
     329             :             // reached till we get much closer
     330          28 :             loiter.start_time_ms = 0;
     331          28 :             quadplane.guided_start();
     332             :         }
     333        8780 :         return;
     334             :     }
     335             : #endif
     336             : 
     337             : #if HAL_QUADPLANE_ENABLED
     338       99875 :     const bool quadplane_qrtl_switch = (control_mode == &mode_rtl && quadplane.available() && quadplane.rtl_mode == QuadPlane::RTL_MODE::SWITCH_QRTL);
     339             : #else
     340             :     const bool quadplane_qrtl_switch = false;
     341             : #endif
     342             : 
     343      230238 :     if ((loiter.start_time_ms == 0 &&
     344       30488 :          (control_mode == &mode_auto || control_mode == &mode_guided) &&
     345       20078 :          auto_state.crosstrack &&
     346      130363 :          current_loc.get_distance(next_WP_loc) > 3 * nav_controller->loiter_radius(radius)) ||
     347             :         quadplane_qrtl_switch) {
     348             :         /*
     349             :           if never reached loiter point and using crosstrack and somewhat far away from loiter point
     350             :           navigate to it like in auto-mode for normal crosstrack behavior
     351             : 
     352             :           we also use direct waypoint navigation if we are a quadplane
     353             :           that is going to be switching to QRTL when it gets within
     354             :           RTL_RADIUS
     355             :         */
     356        3093 :         nav_controller->update_waypoint(prev_WP_loc, next_WP_loc);
     357        3093 :         return;
     358             :     }
     359       96782 :     nav_controller->update_loiter(next_WP_loc, radius, loiter.direction);
     360             : }
     361             : 
     362      108655 : void Plane::update_loiter(uint16_t radius)
     363             : {
     364      108655 :     if (radius <= 1) {
     365             :         // if radius is <=1 then use the general loiter radius. if it's small, use default
     366       95162 :         radius = (abs(aparm.loiter_radius) <= 1) ? LOITER_RADIUS_DEFAULT : abs(aparm.loiter_radius);
     367       95162 :         if (next_WP_loc.loiter_ccw == 1) {
     368           0 :             loiter.direction = -1;
     369             :         } else {
     370       95162 :             loiter.direction = (aparm.loiter_radius < 0) ? -1 : 1;
     371             :         }
     372             :     }
     373             : 
     374             :     // the radius actually being used by the controller is required by other functions
     375      108655 :     loiter.radius = (float)radius;
     376             : 
     377      108655 :     update_loiter_update_nav(radius);
     378             : 
     379      108655 :     if (loiter.start_time_ms == 0) {
     380       60745 :         if (reached_loiter_target() ||
     381       30229 :             auto_state.wp_proportion > 1) {
     382             :             // we've reached the target, start the timer
     383         287 :             loiter.start_time_ms = millis();
     384         287 :             if (control_mode->is_guided_mode()) {
     385             :                 // starting a loiter in GUIDED means we just reached the target point
     386         128 :                 gcs().send_mission_item_reached_message(0);
     387             :             }
     388             : #if HAL_QUADPLANE_ENABLED
     389         287 :             if (quadplane.guided_mode_enabled()) {
     390          56 :                 quadplane.guided_start();
     391             :             }
     392             : #endif
     393             :         }
     394             :     }
     395      108655 : }
     396             : 
     397             : /*
     398             :   handle speed and height control in FBWB, CRUISE, and optionally, LOITER mode.
     399             :   In this mode the elevator is used to change target altitude. The
     400             :   throttle is used to change target airspeed or throttle
     401             :  */
     402      195631 : void Plane::update_fbwb_speed_height(void)
     403             : {
     404      195631 :     uint32_t now = micros();
     405      195631 :     if (now - target_altitude.last_elev_check_us >= 100000) {
     406             :         // we don't run this on every loop as it would give too small granularity on quadplanes at 300Hz, and
     407             :         // give below 1cm altitude change, which would result in no climb or descent
     408       13030 :         float dt = (now - target_altitude.last_elev_check_us) * 1.0e-6;
     409       13030 :         dt = constrain_float(dt, 0.1, 0.15);
     410             : 
     411       13030 :         target_altitude.last_elev_check_us = now;
     412             : 
     413       13030 :         float elevator_input = channel_pitch->get_control_in() * (1/4500.0);
     414             : 
     415       13030 :         if (g.flybywire_elev_reverse) {
     416           0 :             elevator_input = -elevator_input;
     417             :         }
     418             : 
     419       13030 :         bool input_stop_climb = !is_positive(elevator_input) && is_positive(target_altitude.last_elevator_input);
     420       13030 :         bool input_stop_descent = !is_negative(elevator_input) && is_negative(target_altitude.last_elevator_input);
     421       13030 :         if (input_stop_climb || input_stop_descent) {
     422             :             // user elevator input reached or passed zero, lock in the current altitude
     423          45 :             set_target_altitude_current();
     424             :         }
     425             : 
     426       13030 :         float climb_rate = g.flybywire_climb_rate * elevator_input;
     427       13030 :         climb_rate = constrain_float(climb_rate, -TECS_controller.get_max_sinkrate(), TECS_controller.get_max_climbrate());
     428             : 
     429       13030 :         int32_t alt_change_cm = climb_rate * dt * 100;
     430       13030 :         change_target_altitude(alt_change_cm);
     431             : 
     432             : #if HAL_SOARING_ENABLED
     433       13030 :         if (g2.soaring_controller.is_active()) {
     434        1472 :             if (g2.soaring_controller.get_throttle_suppressed()) {
     435             :                 // we're in soaring mode with throttle suppressed
     436        1151 :                 set_target_altitude_current();
     437             :             } else {
     438             :                 // we're in soaring mode climbing back to altitude. Set target to SOAR_ALT_CUTOFF plus 10m to ensure we positively climb
     439             :                 // through SOAR_ALT_CUTOFF, thus triggering throttle suppression and return to glide.
     440         321 :                 target_altitude.amsl_cm = 100*plane.g2.soaring_controller.get_alt_cutoff() + 1000 + AP::ahrs().get_home().alt;
     441             :             }
     442             :         }
     443             : #endif
     444             : 
     445       13030 :         target_altitude.last_elevator_input = elevator_input;
     446             :     }
     447             : 
     448      195631 :     check_fbwb_altitude();
     449             : 
     450      195631 :     calc_throttle();
     451      195631 :     calc_nav_pitch();
     452      195631 : }
     453             : 
     454             : /*
     455             :   calculate the turn angle for the next leg of the mission
     456             :  */
     457        4165 : void Plane::setup_turn_angle(void)
     458             : {
     459        4165 :     int32_t next_ground_course_cd = mission.get_next_ground_course_cd(-1);
     460        4165 :     if (next_ground_course_cd == -1) {
     461             :         // the mission library can't determine a turn angle, assume 90 degrees
     462         252 :         auto_state.next_turn_angle = 90.0f;
     463             :     } else {
     464             :         // get the heading of the current leg
     465        3913 :         int32_t ground_course_cd = prev_WP_loc.get_bearing_to(next_WP_loc);
     466             : 
     467             :         // work out the angle we need to turn through
     468        3913 :         auto_state.next_turn_angle = wrap_180_cd(next_ground_course_cd - ground_course_cd) * 0.01f;
     469             :     }
     470        4165 : }
     471             : 
     472             : /*
     473             :   see if we have reached our loiter target
     474             :  */
     475     1097478 : bool Plane::reached_loiter_target(void)
     476             : {
     477             : #if HAL_QUADPLANE_ENABLED
     478     1097478 :     if (quadplane.in_vtol_auto()) {
     479       66459 :         return auto_state.wp_distance < 3;
     480             :     }
     481             : #endif
     482     1031019 :     return nav_controller->reached_loiter_target();
     483             : }

Generated by: LCOV version 1.14