#include "mode.h" #include "Plane.h" bool ModeRTL::_enter() { plane.prev_WP_loc = plane.current_loc; plane.do_RTL(plane.get_RTL_altitude_cm()); plane.rtl.done_climb = false; #if HAL_QUADPLANE_ENABLED plane.vtol_approach_s.approach_stage = Plane::VTOLApproach::Stage::RTL; // Quadplane specific checks if (plane.quadplane.available()) { // treat RTL as QLAND if we are in guided wait takeoff state, to cope // with failsafes during GUIDED->AUTO takeoff sequence if (plane.quadplane.guided_wait_takeoff_on_mode_enter) { plane.set_mode(plane.mode_qland, ModeReason::QLAND_INSTEAD_OF_RTL); return true; } // if Q_RTL_MODE is QRTL always, immediately switch to QRTL mode if (plane.quadplane.rtl_mode == QuadPlane::RTL_MODE::QRTL_ALWAYS) { plane.set_mode(plane.mode_qrtl, ModeReason::QRTL_INSTEAD_OF_RTL); return true; } // if VTOL landing is expected and quadplane motors are active and within QRTL radius and under QRTL altitude then switch to QRTL const bool vtol_landing = (plane.quadplane.rtl_mode == QuadPlane::RTL_MODE::SWITCH_QRTL) || (plane.quadplane.rtl_mode == QuadPlane::RTL_MODE::VTOL_APPROACH_QRTL); if (vtol_landing && (quadplane.motors->get_desired_spool_state() == AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED)) { int32_t alt_cm; if ((plane.current_loc.get_distance(plane.next_WP_loc) < plane.mode_qrtl.get_VTOL_return_radius()) && plane.current_loc.get_alt_cm(Location::AltFrame::ABOVE_HOME, alt_cm) && (alt_cm < plane.quadplane.qrtl_alt*100)) { plane.set_mode(plane.mode_qrtl, ModeReason::QRTL_INSTEAD_OF_RTL); return true; } } } #endif return true; } void ModeRTL::update() { plane.calc_nav_roll(); plane.calc_nav_pitch(); plane.calc_throttle(); bool alt_threshold_reached = false; if (plane.flight_option_enabled(FlightOptions::CLIMB_BEFORE_TURN)) { // Climb to RTL_ALTITUDE before turning. This overrides RTL_CLIMB_MIN. alt_threshold_reached = plane.current_loc.alt > plane.next_WP_loc.alt; } else if (plane.g2.rtl_climb_min > 0) { /* when RTL first starts limit bank angle to LEVEL_ROLL_LIMIT until we have climbed by RTL_CLIMB_MIN meters */ alt_threshold_reached = (plane.current_loc.alt - plane.prev_WP_loc.alt)*0.01 > plane.g2.rtl_climb_min; } else { return; } if (!plane.rtl.done_climb && alt_threshold_reached) { plane.prev_WP_loc = plane.current_loc; plane.setup_glide_slope(); plane.rtl.done_climb = true; } if (!plane.rtl.done_climb) { // Constrain the roll limit as a failsafe, that way if something goes wrong the plane will // eventually turn back and go to RTL instead of going perfectly straight. This also leaves // some leeway for fighting wind. plane.roll_limit_cd = MIN(plane.roll_limit_cd, plane.g.level_roll_limit*100); plane.nav_roll_cd = constrain_int32(plane.nav_roll_cd, -plane.roll_limit_cd, plane.roll_limit_cd); } } void ModeRTL::navigate() { #if HAL_QUADPLANE_ENABLED if (plane.quadplane.available()) { if (plane.quadplane.rtl_mode == QuadPlane::RTL_MODE::VTOL_APPROACH_QRTL) { // VTOL approach landing AP_Mission::Mission_Command cmd; cmd.content.location = plane.next_WP_loc; plane.verify_landing_vtol_approach(cmd); if (plane.vtol_approach_s.approach_stage == Plane::VTOLApproach::Stage::VTOL_LANDING) { plane.set_mode(plane.mode_qrtl, ModeReason::RTL_COMPLETE_SWITCHING_TO_VTOL_LAND_RTL); } return; } if ((AP_HAL::millis() - plane.last_mode_change_ms > 1000) && switch_QRTL()) { return; } } #endif uint16_t radius = abs(plane.g.rtl_radius); if (radius > 0) { plane.loiter.direction = (plane.g.rtl_radius < 0) ? -1 : 1; } plane.update_loiter(radius); if (!plane.auto_state.checked_for_autoland) { if ((plane.g.rtl_autoland == RtlAutoland::RTL_IMMEDIATE_DO_LAND_START) || (plane.g.rtl_autoland == RtlAutoland::RTL_THEN_DO_LAND_START && plane.reached_loiter_target() && labs(plane.calc_altitude_error_cm()) < 1000)) { // we've reached the RTL point, see if we have a landing sequence if (plane.have_position && plane.mission.jump_to_landing_sequence(plane.current_loc)) { // switch from RTL -> AUTO plane.mission.set_force_resume(true); if (plane.set_mode(plane.mode_auto, ModeReason::RTL_COMPLETE_SWITCHING_TO_FIXEDWING_AUTOLAND)) { // return here so we don't change the radius and don't run the rtl update_loiter() return; } // mode change failed, revert force resume flag plane.mission.set_force_resume(false); } // prevent running the expensive jump_to_landing_sequence // on every loop plane.auto_state.checked_for_autoland = true; } else if (plane.g.rtl_autoland == RtlAutoland::DO_RETURN_PATH_START) { if (plane.have_position && plane.mission.jump_to_closest_mission_leg(plane.current_loc)) { plane.mission.set_force_resume(true); if (plane.set_mode(plane.mode_auto, ModeReason::RTL_COMPLETE_SWITCHING_TO_FIXEDWING_AUTOLAND)) { // return here so we don't change the radius and don't run the rtl update_loiter() return; } // mode change failed, revert force resume flag plane.mission.set_force_resume(false); } plane.auto_state.checked_for_autoland = true; } } } #if HAL_QUADPLANE_ENABLED // Switch to QRTL if enabled and within radius bool ModeRTL::switch_QRTL() { if (plane.quadplane.rtl_mode != QuadPlane::RTL_MODE::SWITCH_QRTL) { return false; } uint16_t qrtl_radius = abs(plane.g.rtl_radius); if (qrtl_radius == 0) { qrtl_radius = abs(plane.aparm.loiter_radius); } if (plane.nav_controller->reached_loiter_target() || plane.current_loc.past_interval_finish_line(plane.prev_WP_loc, plane.next_WP_loc) || plane.auto_state.wp_distance < MAX(qrtl_radius, plane.quadplane.stopping_distance())) { /* for a quadplane in RTL mode we switch to QRTL when we are within the maximum of the stopping distance and the RTL_RADIUS */ plane.set_mode(plane.mode_qrtl, ModeReason::RTL_COMPLETE_SWITCHING_TO_VTOL_LAND_RTL); return true; } return false; } #endif // HAL_QUADPLANE_ENABLED