diff --git a/src/lib/systemlib/system_params.yaml b/src/lib/systemlib/system_params.yaml
index ac8547325c..177b44adf8 100644
--- a/src/lib/systemlib/system_params.yaml
+++ b/src/lib/systemlib/system_params.yaml
@@ -138,11 +138,14 @@ parameters:
reboot_required: true
SYS_HAS_NUM_GNSS:
description:
- short: Control if and how many GNSS receivers are expected
+ short: Control if and how many GNSS receivers are required
long: |-
- 0: Disable GNSS redundancy check (any number of GNSS receivers is acceptable).
+ 0: No minimum receiver count required. Position divergence between two receivers
+ and loss of a GNSS receiver still produce a warning but never trigger the
+ COM_GPS_LOSS_ACT failsafe action.
1-N: Require the presence of N GNSS receivers for arming and during flight.
If the active count drops below this value in flight, COM_GPS_LOSS_ACT is triggered.
+ When set to 2, position divergence between the two receivers also triggers COM_GPS_LOSS_ACT.
type: int32
default: 0
min: 0
diff --git a/src/modules/commander/HealthAndArmingChecks/GpsRedundancyCheckTest.cpp b/src/modules/commander/HealthAndArmingChecks/GpsRedundancyCheckTest.cpp
index a4bcb9b5ba..d9ecc33405 100644
--- a/src/modules/commander/HealthAndArmingChecks/GpsRedundancyCheckTest.cpp
+++ b/src/modules/commander/HealthAndArmingChecks/GpsRedundancyCheckTest.cpp
@@ -136,6 +136,20 @@ public:
& (uint64_t)events::px4::enums::health_component_t::gps) != 0;
}
+ // Runs one check cycle and returns true if gnss_lost was set (failsafe would trigger)
+ bool hasGnssLost(bool armed)
+ {
+ vehicle_status_s status{};
+ status.arming_state = armed
+ ? vehicle_status_s::ARMING_STATE_ARMED
+ : vehicle_status_s::ARMING_STATE_DISARMED;
+ Context context{status};
+ failsafe_flags_s failsafe_flags{};
+ Report reporter{failsafe_flags, 0_s};
+ _check->checkAndReport(context, reporter);
+ return failsafe_flags.gnss_lost;
+ }
+
static orb_advert_t _pub0;
static orb_advert_t _pub1;
GpsRedundancyChecks *_check{nullptr};
@@ -198,6 +212,41 @@ TEST_F(GpsRedundancyCheckTest, FixTypeBelow3TreatedAsInactive)
EXPECT_FALSE(hasDivergenceWarning(false));
}
+// Divergence with SYS_HAS_NUM_GNSS=2 and COM_GPS_LOSS_ACT>0 → gnss_lost set (failsafe fires)
+TEST_F(GpsRedundancyCheckTest, DivergenceWithRedundancyRequiredSetsGnssLost)
+{
+ int i = 2;
+ param_set(param_find("SYS_HAS_NUM_GNSS"), &i);
+ i = 1; // Return
+ param_set(param_find("COM_GPS_LOSS_ACT"), &i);
+ delete _check;
+ _check = new GpsRedundancyChecks();
+
+ sensor_gps_s gps0 = makeGps(BASE_LAT, BASE_LON);
+ sensor_gps_s gps1 = makeGps(DIVERGING_LAT, BASE_LON);
+ orb_publish(ORB_ID(sensor_gps), _pub0, &gps0);
+ orb_publish(ORB_ID(sensor_gps), _pub1, &gps1);
+ EXPECT_TRUE(hasGnssLost(false));
+}
+
+// Divergence with SYS_HAS_NUM_GNSS=0 and COM_GPS_LOSS_ACT>0 → warning only, gnss_lost not set
+TEST_F(GpsRedundancyCheckTest, DivergenceWithoutRedundancyRequiredWarnsOnly)
+{
+ int i = 0;
+ param_set(param_find("SYS_HAS_NUM_GNSS"), &i);
+ i = 1; // Return
+ param_set(param_find("COM_GPS_LOSS_ACT"), &i);
+ delete _check;
+ _check = new GpsRedundancyChecks();
+
+ sensor_gps_s gps0 = makeGps(BASE_LAT, BASE_LON);
+ sensor_gps_s gps1 = makeGps(DIVERGING_LAT, BASE_LON);
+ orb_publish(ORB_ID(sensor_gps), _pub0, &gps0);
+ orb_publish(ORB_ID(sensor_gps), _pub1, &gps1);
+ EXPECT_FALSE(hasGnssLost(false));
+ EXPECT_TRUE(hasDivergenceWarning(false));
+}
+
// Divergence then recovery: timer resets and warning stops
TEST_F(GpsRedundancyCheckTest, DivergenceClearsAfterRecovery)
{
diff --git a/src/modules/commander/HealthAndArmingChecks/checks/gpsRedundancyCheck.cpp b/src/modules/commander/HealthAndArmingChecks/checks/gpsRedundancyCheck.cpp
index 243b4f1c9e..4375f3e15a 100644
--- a/src/modules/commander/HealthAndArmingChecks/checks/gpsRedundancyCheck.cpp
+++ b/src/modules/commander/HealthAndArmingChecks/checks/gpsRedundancyCheck.cpp
@@ -75,12 +75,16 @@ void GpsRedundancyChecks::checkAndReport(const Context &context, Report &reporte
}
}
+ const int required = _param_sys_has_num_gnss.get();
+
// Position divergence check: warn if two fixed receivers disagree beyond their combined
// uncertainty. The gate is: lever-arm separation + GNSS_DIVERGENCE_SIGMA * RMS(eph0, eph1).
// Using RMS(eph) means the gate tightens when both receivers are accurate and widens
// automatically when one degrades, avoiding false alarms without a hard-coded threshold.
// Pre-arm: warn immediately. In-flight: require GPS_DIVERGENCE_SUSTAIN to suppress
// transient multipath spikes.
+ // The failsafe action is only triggered when SYS_HAS_NUM_GNSS >= 2; otherwise divergence
+ // is reported as a warning only.
bool divergence_active = false;
if (fixed_count >= 2) {
@@ -106,8 +110,8 @@ void GpsRedundancyChecks::checkAndReport(const Context &context, Report &reporte
}
if (hrt_elapsed_time(&_divergence_since) >= sustain) {
- divergence_active = true;
- const bool act = (_param_com_gnss_loss_act.get() > 0);
+ const bool act = (_param_com_gnss_loss_act.get() > 0) && (required >= 2);
+ divergence_active = act;
/* EVENT
* @description
@@ -115,6 +119,7 @@ void GpsRedundancyChecks::checkAndReport(const Context &context, Report &reporte
*
*
* Configure the failsafe action with COM_GPS_LOSS_ACT.
+ * The failsafe action is only triggered when SYS_HAS_NUM_GNSS is set to 2.
*
*/
reporter.healthFailure(act ? NavModes::All : NavModes::None,
@@ -135,7 +140,6 @@ void GpsRedundancyChecks::checkAndReport(const Context &context, Report &reporte
_peak_fixed_count = fixed_count;
}
- const int required = _param_sys_has_num_gnss.get();
const bool below_required = (required > 0 && fixed_count < required);
const bool dropped_below_peak = (_peak_fixed_count > 1 && fixed_count < _peak_fixed_count);
diff --git a/src/modules/commander/commander_params.yaml b/src/modules/commander/commander_params.yaml
index d24873fe3c..3bd7b96e35 100644
--- a/src/modules/commander/commander_params.yaml
+++ b/src/modules/commander/commander_params.yaml
@@ -136,8 +136,9 @@ parameters:
short: GNSS loss failsafe mode
long: |-
Action the system takes when a GNSS failure is detected during flight.
- Triggers when the active GNSS count drops below SYS_HAS_NUM_GNSS or when
- two receivers report positions inconsistent with their reported accuracy.
+ Triggers when the active GNSS count drops below SYS_HAS_NUM_GNSS (disabled
+ when SYS_HAS_NUM_GNSS=0), or when two receivers report positions inconsistent
+ with their reported accuracy and SYS_HAS_NUM_GNSS=2 (otherwise warning only).
Any value above Warning also blocks arming.
type: enum
values: