Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 69 additions & 24 deletions docs/en/flight_modes/return.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/en/releases/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ Please continue reading for [upgrade instructions](#upgrade-guide).

- Added new flight mode(s): [Altitude Cruise (MC)](../flight_modes_mc/altitude_cruise.md), Altitude Cruise (FW).
For fixed-wing the mode behaves the same as Altitude mode but you can disable the manual control loss failsafe. ([PX4-Autopilot#25435: Add new flight mode: Altitude Cruise](https://github.com/PX4/PX4-Autopilot/pull/25435)).
- Added `RTL_TYPE=6` for battery-aware home priority return ([PX4-Autopilot#26968](https://github.com/PX4/PX4-Autopilot/pull/26968)).
Returns to home if the estimated flight time to home is within the remaining battery time; otherwise returns to the closest rally point.
Falls back to the closest safe point (home or rally) if battery time remaining is unavailable.

### Estimation

Expand Down
52 changes: 49 additions & 3 deletions src/modules/navigator/rtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ void RTL::on_inactive()
_mission_sub.update();
_home_pos_sub.update();
_wind_sub.update();
_battery_status_sub.update();

updateDatamanCache();

Expand Down Expand Up @@ -225,7 +226,7 @@ void RTL::on_activation()
_mission_sub.update();
_home_pos_sub.update();
_wind_sub.update();

_battery_status_sub.update();
setRtlTypeAndDestination();

switch (_rtl_type) {
Expand Down Expand Up @@ -265,7 +266,6 @@ void RTL::on_active()
_mission_sub.update();
_home_pos_sub.update();
_wind_sub.update();

updateDatamanCache();

switch (_rtl_type) {
Expand Down Expand Up @@ -368,6 +368,52 @@ void RTL::setRtlTypeAndDestination()
new_rtl_type = RtlType::RTL_DIRECT;
}

} else if (_param_rtl_type.get() == 6) {
// Set _rtl_direct with the home destination so calc_rtl_time_estimate() can check its reachability
const float rtl_alt_home = computeReturnAltitude(destination);
_rtl_direct.setRtlAlt(rtl_alt_home);
Comment thread
anilkir marked this conversation as resolved.
_rtl_direct.setRtlPosition(destination, landing_loiter);

const rtl_time_estimate_s time_to_home = _rtl_direct.calc_rtl_time_estimate();
const float battery_remaining_s = _battery_status_sub.get().time_remaining_s;

const bool home_within_reach = time_to_home.valid
&& PX4_ISFINITE(battery_remaining_s)
&& (time_to_home.safe_time_estimate < battery_remaining_s);

if (!home_within_reach) {
// If battery data is valid, home is out of range: pick the closest rally point unconditionally
// If battery data is unavailable (NaN), we cannot assess reachability: pick the closest home or rally point
const float min_dist = PX4_ISFINITE(battery_remaining_s)
? FLT_MAX
: get_distance_to_next_waypoint(_global_pos_sub.get().lat,
_global_pos_sub.get().lon,
_home_pos_sub.get().lat,
_home_pos_sub.get().lon);

PositionYawSetpoint safe_point = findClosestSafePoint(min_dist, safe_point_index);

if (safe_point_index != UINT8_MAX) {
destination = safe_point;
destination_type = DestinationType::DESTINATION_TYPE_SAFE_POINT;
}

// If no rally points are closer (or none are defined), fall back to home (already set as the destination)
}

new_rtl_type = RtlType::RTL_DIRECT;

land_approaches_s rtl_land_approaches{readVtolLandApproaches(destination)};
landing_loiter.lat = destination.lat;
landing_loiter.lon = destination.lon;
landing_loiter.height_m = NAN;

if (_vehicle_status_sub.get().is_vtol
&& (_vehicle_status_sub.get().vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING)
&& rtl_land_approaches.isAnyApproachValid()) {
landing_loiter = chooseBestLandingApproach(rtl_land_approaches);
}

} else {
// check the closest allowed destination.
findRtlDestination(destination_type, destination, safe_point_index);
Expand Down Expand Up @@ -450,7 +496,7 @@ PositionYawSetpoint RTL::findClosestSafePoint(float min_dist, uint8_t &safe_poin
const bool far_from_home = get_distance_to_next_waypoint(_home_pos_sub.get().lat, _home_pos_sub.get().lon,
mission_safe_point.lat, mission_safe_point.lon) > MAX_DIST_FROM_HOME_FOR_LAND_APPROACHES;

if (far_from_home || (_param_rtl_type.get() == 5)) {
if (mission_safe_point.nav_cmd == NAV_CMD_RALLY_POINT && (far_from_home || (_param_rtl_type.get() == 5) || (_param_rtl_type.get() == 6))) {
const float dist{get_distance_to_next_waypoint(_global_pos_sub.get().lat, _global_pos_sub.get().lon, mission_safe_point.lat, mission_safe_point.lon)};

PositionYawSetpoint safepoint_position;
Expand Down
2 changes: 2 additions & 0 deletions src/modules/navigator/rtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/rtl_status.h>
#include <uORB/topics/rtl_time_estimate.h>
#include <uORB/topics/battery_status.h>

class Navigator;

Expand Down Expand Up @@ -248,6 +249,7 @@ class RTL : public NavigatorMode, public ModuleParams
uORB::SubscriptionData<mission_s> _mission_sub{ORB_ID(mission)};
uORB::SubscriptionData<home_position_s> _home_pos_sub{ORB_ID(home_position)};
uORB::SubscriptionData<wind_s> _wind_sub{ORB_ID(wind)};
uORB::SubscriptionData<battery_status_s> _battery_status_sub{ORB_ID(battery_status)};

uORB::Publication<rtl_time_estimate_s> _rtl_time_estimate_pub{ORB_ID(rtl_time_estimate)};
uORB::Publication<rtl_status_s> _rtl_status_pub{ORB_ID(rtl_status)};
Expand Down
5 changes: 4 additions & 1 deletion src/modules/navigator/rtl_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ parameters:
Skip DO_JUMP and other non-position mission items while following either
mission path. Do not consider rally points.
5: Return directly to safe landing point (do not consider mission landing
and Home)
and Home).
6: Return to home if time estimate to home is less than battery remaining estimate,
else return to the closest rally point. If battery remaining estimate is not available,
return to the closest safe point (home or rally point).
default: 0
RTL_CONE_ANG:
description:
Expand Down
1 change: 1 addition & 0 deletions test/mavsdk_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ if(MAVSDK_FOUND)
test_multicopter_failure_injection.cpp
test_multicopter_failsafe.cpp
test_multicopter_mission.cpp
test_multicopter_rtl.cpp
test_multicopter_offboard.cpp
test_multicopter_manual.cpp
test_vtol_mission.cpp
Expand Down
95 changes: 95 additions & 0 deletions test/mavsdk_tests/test_multicopter_rtl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/

#include "autopilot_tester_rtl.h"


// TODO: Add test cases for these scenarios:
// - "RTL type 6 home within reach with no rally points", "[multicopter]"
// - "RTL type 6 home within reach with rally points and home is the closest", "[multicopter]"
// - "RTL type 6 home within reach with rally points and home is not the closest", "[multicopter]"
// - "RTL type 6 home out of reach with rally points and at least one rally point within reach", "[multicopter]"

TEST_CASE("RTL_TYPE=6 time_remaining_s NaN with no rally points", "[multicopter]")
{
AutopilotTesterRtl tester;
tester.connect(connection_url);
tester.wait_until_ready();
tester.store_home();
tester.set_rtl_type(6);
tester.arm();
tester.takeoff();
tester.wait_until_hovering();
tester.execute_rtl();
tester.wait_until_disarmed(std::chrono::seconds(90));
tester.check_home_within(5.0f);
}

TEST_CASE("RTL_TYPE=6 time_remaining_s NaN with rally points and home is closest", "[multicopter]")
{
AutopilotTesterRtl tester;
tester.connect(connection_url);
tester.wait_until_ready();
tester.store_home();
tester.set_rtl_type(6);
// Rally is farther than home; vehicle is above home after takeoff so home wins on distance
tester.add_local_rally_point({100., -100.});
tester.upload_rally_points();
tester.arm();
tester.takeoff();
tester.wait_until_hovering();
tester.execute_rtl();
tester.wait_until_disarmed(std::chrono::seconds(90));
tester.check_home_within(5.0f);
}

TEST_CASE("RTL_TYPE=6 time_remaining_s NaN with rally points and home is not closest", "[multicopter]")
{
AutopilotTesterRtl tester;
tester.connect(connection_url);
tester.wait_until_ready();
tester.store_home();
tester.set_rtl_type(6);
// Rally point is between the vehicle and home, so rally point wins on distance
tester.add_local_rally_point({300., 0.});
tester.upload_rally_points();
tester.arm();
tester.takeoff();
tester.wait_until_hovering();
// Distance to home is 400m, distance to rally is 100m
tester.offboard_goto({400.f, 0.f, -20.f}, 5.f, std::chrono::seconds(60));
tester.execute_rtl();
tester.wait_until_disarmed(std::chrono::seconds(150));
tester.check_rally_point_within(5.0f);
tester.check_home_not_within(20.0f);
}
Loading