LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - mode_autoland.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 98 107 91.6 %
Date: 2026-09-09 11:02:08 Functions: 8 9 88.9 %

          Line data    Source code
       1             : #include "mode.h"
       2             : #include "Plane.h"
       3             : #include <GCS_MAVLink/GCS.h>
       4             : 
       5             : #if MODE_AUTOLAND_ENABLED
       6             : 
       7             : // This is added to the target altitude to make sure the true target is exceeded.
       8             : // This should be larger than the expected steady state error.
       9             : constexpr float fast_climb_extra_alt = 10;
      10             : 
      11             : /*
      12             :   mode AutoLand parameters
      13             :  */
      14             : const AP_Param::GroupInfo ModeAutoLand::var_info[] = {
      15             :     // @Param: WP_ALT
      16             :     // @DisplayName: Final approach WP altitude
      17             :     // @Description: This is the target altitude above HOME for final approach waypoint
      18             :     // @Range: 0 200
      19             :     // @Increment: 1
      20             :     // @Units: m
      21             :     // @User: Standard
      22             :     AP_GROUPINFO("WP_ALT", 1, ModeAutoLand, final_wp_alt, 55),
      23             : 
      24             :     // @Param: WP_DIST
      25             :     // @DisplayName: Final approach WP distance
      26             :     // @Description: This is the distance from Home that the final approach waypoint is set. The waypoint point will be in the opposite direction of takeoff (the direction the plane is facing when the plane sets its takeoff heading)
      27             :     // @Range: 0 700
      28             :     // @Increment: 1
      29             :     // @Units: m
      30             :     // @User: Standard
      31             :     AP_GROUPINFO("WP_DIST", 2, ModeAutoLand, final_wp_dist, 400),
      32             : 
      33             :     // @Param: DIR_OFF
      34             :     // @DisplayName: Landing direction offset from takeoff
      35             :     // @Description: The captured takeoff direction after ground course is established in autotakeoffsis offset by this amount to create a different landing direction and approach.However,if TKOFF_OPTION bit1 is set, the takeoff(landing) direction is captured immediately via compass heading upon arming, then this offset is NOT applied.
      36             :     // @Range: -360 360
      37             :     // @Increment: 1
      38             :     // @Units: deg
      39             :     // @User: Standard
      40             :     AP_GROUPINFO("DIR_OFF", 3, ModeAutoLand, landing_dir_off, 0),
      41             : 
      42             :     // @Param: OPTIONS
      43             :     // @DisplayName: Autoland mode options
      44             :     // @Description: Enables optional autoland mode behaviors
      45             :     // @Bitmask: 0: When set if there is a healthy compass in use the compass heading will be captured at arming and used for the AUTOLAND mode's initial takeoff direction instead of capturing ground course in NAV_TAKEOFF or Mode TAKEOFF or other modes.
      46             :     // @User: Standard
      47             :     AP_GROUPINFO("OPTIONS", 4, ModeAutoLand, options, 0),
      48             : 
      49             :     // @Param: CLIMB
      50             :     // @DisplayName: Minimum altitude above terrain before turning upon entry
      51             :     // @Description: Vehicle will climb with limited turn ability (LEVEL_ROLL_LIMIT) until it is at least this altitude above the terrain at the point of entry, before proceeding to loiter-to-alt and landing legs. 0 Disables.
      52             :     // @Range: 0 100
      53             :     // @Increment: 1
      54             :     // @Units: m
      55             :     // @User: Standard
      56             :     AP_GROUPINFO("CLIMB", 5, ModeAutoLand, terrain_alt_min, 0),
      57             : 
      58             : 
      59             :     AP_GROUPEND
      60             : };
      61             : 
      62         692 : ModeAutoLand::ModeAutoLand() :
      63         692 :     Mode()
      64             : {
      65         692 :     AP_Param::setup_object_defaults(this, var_info);
      66         692 : }
      67             : 
      68           4 : bool ModeAutoLand::_enter()
      69             : {
      70             :     //must be flying to enter
      71           4 :     if (!plane.is_flying()) {
      72           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "Must already be flying!");
      73           0 :         return false;
      74             :     }
      75             : 
      76             :     // autoland not available for quadplanes as capture of takeoff direction
      77             :     // doesn't make sense
      78             : #if HAL_QUADPLANE_ENABLED
      79           4 :     if (quadplane.available()) {
      80           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "autoland not available");
      81           0 :         return false;
      82             :     }
      83             : #endif
      84             : 
      85           4 :     if (!plane.takeoff_state.initial_direction.initialized) {
      86           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "Takeoff initial direction not set");
      87           0 :         return false;
      88             :     }
      89             : 
      90           4 :     plane.set_target_altitude_current();
      91           4 :     plane.auto_state.next_wp_crosstrack = true;
      92             : 
      93           4 :     plane.prev_WP_loc = plane.current_loc;
      94             : 
      95             :     // In flight stage normal for approach
      96           4 :     plane.set_flight_stage(AP_FixedWing::FlightStage::NORMAL);
      97             : 
      98           4 :     const Location &home = ahrs.get_home();
      99             : 
     100             : #ifndef HAL_LANDING_DEEPSTALL_ENABLED
     101             :     if (plane.landing.get_type() == AP_Landing::TYPE_DEEPSTALL) {
     102             :         // Deep stall landings require only a landing location, they do there own loiter to alt and approach
     103             :         cmd_land.id = MAV_CMD_NAV_LAND;
     104             :         cmd_land.content.location = home;
     105             : 
     106             :         // p1 gives the altitude from which to start the deep stall above the location alt
     107             :         cmd_land.p1 = final_wp_alt;
     108             :         plane.start_command(cmd_land);
     109             : 
     110             :         stage = AutoLandStage::LANDING;
     111             :         return true;
     112             :     }
     113             : #endif // HAL_LANDING_DEEPSTALL_ENABLED
     114             : 
     115             :     /*
     116             :       Glide slope landing is in 3 steps:
     117             :         1) a loitering to alt waypoint centered on base leg
     118             :         2) exiting and proceeeing to a final approach land start WP, with crosstrack
     119             :         3) the landing WP at home, with crosstrack
     120             : 
     121             :       the base leg point is 90 degrees off from the landing leg
     122             :      */
     123             : 
     124             :     /*
     125             :       first calculate the starting waypoint we will use when doing the
     126             :       NAV_LAND. This is offset by final_wp_dist from home, in a
     127             :       direction 180 degrees from takeoff direction
     128             :      */
     129           4 :     land_start = home;
     130           4 :     land_start.offset_bearing(plane.takeoff_state.initial_direction.heading, -final_wp_dist);
     131           4 :     land_start.set_alt_m(final_wp_alt, Location::AltFrame::ABOVE_HOME);
     132           4 :     land_start.change_alt_frame(Location::AltFrame::ABSOLUTE);
     133             : 
     134             :     /*
     135             :       now create the initial target waypoint for the loitering to alt centered on base leg waypoint. We
     136             :       choose if we will do a right or left turn onto the landing based
     137             :       on where we are when we enter the landing mode
     138             :      */
     139           4 :     const float bearing_to_current_deg = degrees(land_start.get_bearing(plane.current_loc));
     140           4 :     const float bearing_err_deg = wrap_180(plane.takeoff_state.initial_direction.heading - bearing_to_current_deg);
     141           4 :     const float bearing_offset_deg = (bearing_err_deg > 0) ? -90 : 90;
     142             : 
     143             :     // Try and minimize loiter radius by using the smaller of the waypoint loiter radius or 1/3 of the final WP distance
     144           4 :     const float loiter_radius = MIN(final_wp_dist * 0.333, abs(plane.aparm.loiter_radius));
     145             : 
     146             :     // corrected_loiter_radius is the radius the vehicle will actually fly, this gets larger as altitude increases.
     147             :     // Strictly this gets the loiter radius at the current altitude, really we want the loiter radius at final_wp_alt.
     148           4 :     const float corrected_loiter_radius = plane.nav_controller->loiter_radius(loiter_radius);
     149             : 
     150           4 :     cmd_loiter.id = MAV_CMD_NAV_LOITER_TO_ALT;
     151           4 :     cmd_loiter.p1 = loiter_radius;
     152           4 :     cmd_loiter.content.location = land_start;
     153           4 :     cmd_loiter.content.location.offset_bearing(plane.takeoff_state.initial_direction.heading + bearing_offset_deg, corrected_loiter_radius);
     154           4 :     cmd_loiter.content.location.loiter_ccw = bearing_err_deg>0? 1 :0;
     155             : 
     156             :     // May need to climb first
     157           4 :     bool climb_first = false;
     158           4 :     if (terrain_alt_min > 0) {
     159             :         // Work out the distance needed to climb above terrain
     160             : #if AP_TERRAIN_AVAILABLE
     161           1 :         const bool use_terrain = plane.terrain_enabled_in_current_mode();
     162             : #else
     163             :         const bool use_terrain = false;
     164             : #endif
     165           1 :         const float dist_to_climb = terrain_alt_min - plane.relative_ground_altitude(RangeFinderUse::CLIMB, use_terrain);
     166           1 :         if (is_positive(dist_to_climb)) {
     167             :             // Copy loiter and update target altitude to current altitude plus climb altitude
     168           1 :             cmd_climb = cmd_loiter;
     169             :             float abs_alt;
     170           1 :             if (plane.current_loc.get_alt_m(Location::AltFrame::ABSOLUTE, abs_alt)) {
     171           1 :                 cmd_climb.content.location.set_alt_m(abs_alt + dist_to_climb + fast_climb_extra_alt, Location::AltFrame::ABSOLUTE);
     172           1 :                 climb_first = true;
     173             :             }
     174             :         }
     175             :     }
     176             : 
     177             : #if AP_TERRAIN_AVAILABLE
     178             :     // Update loiter location to be relative terrain if enabled
     179           4 :     if (plane.terrain_enabled_in_current_mode()) {
     180           1 :         cmd_loiter.content.location.set_alt_m(final_wp_alt, Location::AltFrame::ABOVE_TERRAIN);
     181             :     };
     182             : #endif
     183             :     // land WP at home
     184           4 :     cmd_land.id = MAV_CMD_NAV_LAND;
     185           4 :     cmd_land.content.location = home;
     186             : 
     187             :     // start first leg toward the base leg loiter to alt point
     188           4 :     if (climb_first) {
     189           1 :         stage = AutoLandStage::CLIMB;
     190           1 :         plane.start_command(cmd_climb);
     191             : 
     192             :     } else {
     193           3 :         stage = AutoLandStage::LOITER;
     194           3 :         plane.start_command(cmd_loiter);
     195             :     }
     196             : 
     197           4 :     return true;
     198             : }
     199             : 
     200       27866 : void ModeAutoLand::update()
     201             : {
     202       27866 :     plane.calc_nav_roll();
     203             : 
     204             :     // Apply level roll limit in climb stage
     205       27866 :     if (stage == AutoLandStage::CLIMB) {
     206        2223 :         plane.roll_limit_cd = MIN(plane.roll_limit_cd, plane.g.level_roll_limit*100);
     207        2223 :         plane.nav_roll_cd = constrain_int16(plane.nav_roll_cd, -plane.roll_limit_cd, plane.roll_limit_cd);
     208             :     }
     209             : 
     210       27866 :     plane.calc_nav_pitch();
     211       27866 :     if (plane.landing.is_throttle_suppressed()) {
     212             :         // if landing is considered complete throttle is never allowed, regardless of landing type
     213        2545 :         SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, 0.0);
     214             :     } else {
     215       25321 :         plane.calc_throttle();
     216             :     }
     217       27866 : }
     218             : 
     219        5574 : void ModeAutoLand::navigate()
     220             : {
     221        5574 :     switch (stage) {
     222         445 :     case AutoLandStage::CLIMB:
     223             :         // Update loiter, although roll limit is applied the vehicle will still navigate (slowly)
     224         445 :         plane.update_loiter(cmd_climb.p1);
     225             : 
     226             :         ftype dist;
     227         445 :         if (plane.reached_loiter_target() || !cmd_climb.content.location.get_height_above(plane.current_loc, dist) || (dist < fast_climb_extra_alt)) {
     228             :             // Reached destination or Climb is done, move onto loiter
     229           1 :             plane.auto_state.next_wp_crosstrack = true;
     230           1 :             stage = AutoLandStage::LOITER;
     231           1 :             plane.start_command(cmd_loiter);
     232           1 :             plane.prev_WP_loc = plane.current_loc;
     233             :         }
     234         445 :         break;
     235             : 
     236        3960 :     case AutoLandStage::LOITER:
     237             :         // check if we have arrived and completed loiter at base leg waypoint
     238        3960 :         if (plane.verify_loiter_to_alt(cmd_loiter)) {
     239           3 :             stage = AutoLandStage::LANDING;
     240           3 :             plane.start_command(cmd_land);
     241             :             // Crosstrack from the land start location
     242           3 :             plane.prev_WP_loc = land_start;
     243             : 
     244             :         }
     245        3960 :         break;
     246             : 
     247        1169 :     case AutoLandStage::LANDING:
     248        1169 :         plane.set_flight_stage(AP_FixedWing::FlightStage::LAND);
     249        1169 :         plane.verify_command(cmd_land);
     250             :         // make sure we line up
     251        1169 :         plane.auto_state.crosstrack = true;
     252        1169 :         break;
     253             :     }
     254        5574 : }
     255             : 
     256             : /*
     257             :   Takeoff direction is initialized after arm when sufficient altitude
     258             :   and ground speed is obtained, then captured takeoff direction +
     259             :   offset used as landing direction in AUTOLAND
     260             : */
     261     8377547 : void ModeAutoLand::check_takeoff_direction()
     262             : {
     263             : #if HAL_QUADPLANE_ENABLED
     264             :     // we don't allow fixed wing autoland for quadplanes
     265     8377547 :     if (quadplane.available()) {
     266     6504054 :         return;
     267             :     }
     268             : #endif
     269             : 
     270     1873493 :     if (plane.takeoff_state.initial_direction.initialized) {
     271     1226401 :         return;
     272             :     }
     273             :     //set autoland direction to GPS course over ground
     274     1284319 :     if (plane.control_mode->allows_autoland_direction_capture() &&
     275      643323 :         plane.is_flying() &&
     276     1290415 :         hal.util->get_soft_armed() &&
     277        4832 :         plane.gps.ground_speed() > GPS_GND_CRS_MIN_SPD) {
     278         144 :         set_autoland_direction(plane.gps.ground_course() + landing_dir_off);
     279             :     }
     280             : }
     281             : 
     282             : // Sets autoland direction using ground course + offset parameter
     283         144 : void ModeAutoLand::set_autoland_direction(const float heading)
     284             : {
     285         144 :     plane.takeoff_state.initial_direction.heading = wrap_360(heading);
     286         144 :     plane.takeoff_state.initial_direction.initialized = true;
     287         144 :     gcs().send_text(MAV_SEVERITY_INFO, "Autoland direction= %u",int(plane.takeoff_state.initial_direction.heading));
     288         144 : }
     289             : 
     290             : /*
     291             :   return true when the LOITER_TO_ALT is lined up ready for the landing, used in commands_logic verify loiter to alt
     292             :  */
     293         434 : bool ModeAutoLand::landing_lined_up(void)
     294             : {
     295             :     // use the line between the center of the LOITER_TO_ALT on the base leg and the
     296             :     // start of the landing leg (land_start_WP)
     297         434 :     return plane.mode_loiter.isHeadingLinedUp(cmd_loiter.content.location, cmd_land.content.location);
     298             : }
     299             : 
     300             : // possibly capture heading on arming for autoland mode if option is set and using a compass
     301         415 : void ModeAutoLand::arm_check(void)
     302             : {
     303         415 :     if (plane.ahrs.use_compass() && autoland_option_is_set(ModeAutoLand::AutoLandOption::AUTOLAND_DIR_ON_ARM)) {
     304           0 :         set_autoland_direction(plane.ahrs.get_yaw_deg());
     305             :     }
     306         415 : }
     307             : 
     308           0 : bool ModeAutoLand::is_landing() const
     309             : {
     310           0 :     return (plane.flight_stage == AP_FixedWing::FlightStage::LAND);
     311             : }
     312             : 
     313             : 
     314             : #endif // MODE_AUTOLAND_ENABLED
     315             : 

Generated by: LCOV version 1.14