Skip to content

Commit 8fea79c

Browse files
committed
feat(commander): uncommanded altitude loss detection with parachute failsafe
Detects when a rotary-wing vehicle drops more than FD_ALT_LOSS metres below a NED-z reference while altitude control is active, and immediately triggers flight termination (parachute deployment). Detection (FailureDetector): - FD_ALT_LOSS: drop threshold in metres (0 = disabled, default) - FD_ALT_LOSS_T: hysteresis time - Guards: rotary-wing only, altitude control active, z_valid, setpoint fresh (<1 s). Manual, Acro and FW/VTOL-FW modes are excluded. - Ratcheting reference: initialises to lpos.z on first sample below setpoint, preventing false triggers on new waypoints Failsafe action (commander): - New fd_alt_loss flag in FailsafeFlags.msg - COM_ALT_LOSS_ACT: -1=Disabled (default), 0=Terminate - Terminate fires immediately, cannot be overridden, and never clears until disarm (parachute deployment is irreversible)
1 parent 962db50 commit 8fea79c

File tree

6 files changed

+84
-4
lines changed

6 files changed

+84
-4
lines changed

msg/FailsafeFlags.msg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ bool battery_low_remaining_time # Low battery based on remaining flight ti
4545
bool battery_unhealthy # Battery unhealthy
4646

4747
# Failure detector
48-
bool fd_critical_failure # Critical failure (attitude/altitude limit exceeded, or external ATS)
48+
bool fd_critical_failure # Critical failure (attitude limit exceeded, or external ATS)
4949
bool fd_esc_arming_failure # ESC failed to arm
5050
bool fd_imbalanced_prop # Imbalanced propeller detected
5151
bool fd_motor_failure # Motor failure
52+
bool fd_alt_loss # Uncommanded altitude loss (rotary-wing, altitude-controlled flight)
5253

5354
# Other
5455
bool geofence_breached # Geofence breached (one or multiple)

src/modules/commander/HealthAndArmingChecks/checks/failureDetectorCheck.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ void FailureDetectorChecks::checkAndReport(const Context &context, Report &repor
100100
}
101101
}
102102

103-
reporter.failsafeFlags().fd_critical_failure = fd_status.fd_roll || fd_status.fd_pitch || fd_status.fd_alt ||
104-
fd_status.fd_ext;
103+
reporter.failsafeFlags().fd_critical_failure = fd_status.fd_roll || fd_status.fd_pitch || fd_status.fd_ext;
104+
reporter.failsafeFlags().fd_alt_loss = fd_status.fd_alt;
105105

106106
reporter.failsafeFlags().fd_imbalanced_prop = fd_status.fd_imbalanced_prop;
107107

src/modules/commander/failsafe/failsafe.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,12 +624,15 @@ void Failsafe::checkStateAndMode(const hrt_abstime &time_us, const State &state,
624624
+ static_cast<hrt_abstime>((_param_com_lkdown_tko.get() + _param_com_spoolup_time.get()) * 1_s))
625625
) {
626626
CHECK_FAILSAFE(status_flags, fd_critical_failure, ActionOptions(Action::Disarm).cannotBeDeferred());
627+
CHECK_FAILSAFE(status_flags, fd_alt_loss, ActionOptions(Action::Disarm).cannotBeDeferred());
627628

628629
} else if (!circuit_breaker_enabled_by_val(_param_cbrk_flightterm.get(), CBRK_FLIGHTTERM_KEY)) {
629630
CHECK_FAILSAFE(status_flags, fd_critical_failure, ActionOptions(Action::Terminate).cannotBeDeferred());
631+
CHECK_FAILSAFE(status_flags, fd_alt_loss, ActionOptions(Action::Terminate).cannotBeDeferred());
630632

631633
} else {
632634
CHECK_FAILSAFE(status_flags, fd_critical_failure, Action::Warn);
635+
CHECK_FAILSAFE(status_flags, fd_alt_loss, Action::Warn);
633636
}
634637

635638
CHECK_FAILSAFE(status_flags, fd_imbalanced_prop, fromImbalancedPropActParam(_param_com_imb_prop_act.get()));

src/modules/commander/failure_detector/FailureDetector.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ bool FailureDetector::update(const vehicle_status_s &vehicle_status, const vehic
5656

5757
if (vehicle_control_mode.flag_control_attitude_enabled) {
5858
updateAttitudeStatus(vehicle_status);
59+
updateAltitudeStatus(vehicle_status, vehicle_control_mode);
5960

6061
if (_param_fd_ext_ats_en.get()) {
6162
updateExternalAtsStatus();
@@ -66,6 +67,9 @@ bool FailureDetector::update(const vehicle_status_s &vehicle_status, const vehic
6667
_failure_detector_status.flags.pitch = false;
6768
_failure_detector_status.flags.alt = false;
6869
_failure_detector_status.flags.ext = false;
70+
// Reset altitude loss state so it reinitialises cleanly when altitude control re-engages.
71+
_alt_loss_ref_z = NAN;
72+
_alt_loss_hysteresis.set_state_and_update(false, hrt_absolute_time());
6973
}
7074

7175
if (_param_fd_imb_prop_thr.get() > 0) {
@@ -141,6 +145,44 @@ void FailureDetector::updateAttitudeStatus(const vehicle_status_s &vehicle_statu
141145
}
142146
}
143147

148+
void FailureDetector::updateAltitudeStatus(const vehicle_status_s &vehicle_status,
149+
const vehicle_control_mode_s &vehicle_control_mode)
150+
{
151+
const float threshold = _param_fd_alt_loss.get();
152+
153+
if (threshold < FLT_EPSILON
154+
|| !vehicle_control_mode.flag_control_altitude_enabled
155+
|| vehicle_status.vehicle_type != vehicle_status_s::VEHICLE_TYPE_ROTARY_WING) {
156+
_failure_detector_status.flags.alt = false;
157+
_alt_loss_ref_z = NAN;
158+
return;
159+
}
160+
161+
vehicle_local_position_s lpos{};
162+
vehicle_local_position_setpoint_s lpos_sp{};
163+
_vehicle_local_position_sub.copy(&lpos);
164+
_vehicle_local_position_setpoint_sub.copy(&lpos_sp);
165+
166+
const hrt_abstime now = hrt_absolute_time();
167+
168+
if (lpos.z > lpos_sp.z) {
169+
170+
// Ratcheting NED-z reference (mirrors VtolType::isUncommandedDescent in vtol_type.cpp,
171+
// translated to NED: ref_z = max(min(ref_z, lpos.z), lpos_sp.z); trigger when (lpos.z - ref_z) > threshold).
172+
_alt_loss_ref_z = math::max(math::min(_alt_loss_ref_z, lpos.z), lpos_sp.z);
173+
174+
const bool is_below_threshold = (lpos.z - _alt_loss_ref_z) > threshold;
175+
_alt_loss_hysteresis.set_hysteresis_time_from(false, (hrt_abstime)(1_s * _param_fd_alt_loss_ttri.get()));
176+
_alt_loss_hysteresis.set_state_and_update(is_below_threshold, now);
177+
178+
} else {
179+
_alt_loss_ref_z = NAN;
180+
_alt_loss_hysteresis.set_state_and_update(false, now);
181+
}
182+
183+
_failure_detector_status.flags.alt = _alt_loss_hysteresis.get_state();
184+
}
185+
144186
void FailureDetector::updateExternalAtsStatus()
145187
{
146188
pwm_input_s pwm_input;

src/modules/commander/failure_detector/FailureDetector.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
#include <uORB/topics/vehicle_attitude.h>
6161
#include <uORB/topics/vehicle_control_mode.h>
6262
#include <uORB/topics/vehicle_imu_status.h>
63+
#include <uORB/topics/vehicle_local_position.h>
64+
#include <uORB/topics/vehicle_local_position_setpoint.h>
6365
#include <uORB/topics/vehicle_status.h>
6466

6567
union failure_detector_status_u {
@@ -89,22 +91,29 @@ class FailureDetector : public ModuleParams
8991

9092
private:
9193
void updateAttitudeStatus(const vehicle_status_s &vehicle_status);
94+
void updateAltitudeStatus(const vehicle_status_s &vehicle_status,
95+
const vehicle_control_mode_s &vehicle_control_mode);
9296
void updateExternalAtsStatus();
9397
void updateImbalancedPropStatus();
9498

9599
failure_detector_status_u _failure_detector_status{};
96100

97101
systemlib::Hysteresis _roll_failure_hysteresis{false};
98102
systemlib::Hysteresis _pitch_failure_hysteresis{false};
103+
systemlib::Hysteresis _alt_loss_hysteresis{false};
99104
systemlib::Hysteresis _ext_ats_failure_hysteresis{false};
100105

106+
float _alt_loss_ref_z{NAN}; // ratcheting NED-z reference for altitude loss detection
107+
101108
static constexpr float _imbalanced_prop_lpf_time_constant{5.f};
102109
AlphaFilter<float> _imbalanced_prop_lpf{};
103110
uint32_t _selected_accel_device_id{0};
104111
hrt_abstime _imu_status_timestamp_prev{0};
105112

106113

107114
uORB::Subscription _vehicle_attitude_sub{ORB_ID(vehicle_attitude)};
115+
uORB::Subscription _vehicle_local_position_sub{ORB_ID(vehicle_local_position)};
116+
uORB::Subscription _vehicle_local_position_setpoint_sub{ORB_ID(vehicle_local_position_setpoint)};
108117
uORB::Subscription _pwm_input_sub{ORB_ID(pwm_input)};
109118
uORB::Subscription _sensor_selection_sub{ORB_ID(sensor_selection)};
110119
uORB::Subscription _vehicle_imu_status_sub{ORB_ID(vehicle_imu_status)};
@@ -120,6 +129,8 @@ class FailureDetector : public ModuleParams
120129
(ParamFloat<px4::params::FD_FAIL_P_TTRI>) _param_fd_fail_p_ttri,
121130
(ParamBool<px4::params::FD_EXT_ATS_EN>) _param_fd_ext_ats_en,
122131
(ParamInt<px4::params::FD_EXT_ATS_TRIG>) _param_fd_ext_ats_trig,
123-
(ParamInt<px4::params::FD_IMB_PROP_THR>) _param_fd_imb_prop_thr
132+
(ParamInt<px4::params::FD_IMB_PROP_THR>) _param_fd_imb_prop_thr,
133+
(ParamFloat<px4::params::FD_ALT_LOSS>) _param_fd_alt_loss,
134+
(ParamFloat<px4::params::FD_ALT_LOSS_T>) _param_fd_alt_loss_ttri
124135
)
125136
};

src/modules/commander/failure_detector/failure_detector_params.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,26 @@ parameters:
9191
min: 0
9292
max: 1000
9393
increment: 1
94+
FD_ALT_LOSS:
95+
description:
96+
short: Altitude loss threshold for termination and parachute deployment
97+
long: |-
98+
Maximum altitude loss below the setpoint allowed before the vehicle terminates and deploys the parachute. Set to 0 to disable.
99+
type: float
100+
default: 0.0
101+
min: 0.0
102+
max: 200.0
103+
unit: m
104+
decimal: 1
105+
increment: 0.5
106+
FD_ALT_LOSS_T:
107+
description:
108+
short: Altitude loss failure trigger time
109+
long: |-
110+
Seconds that the altitude loss threshold must be exceeded before the failure is declared.
111+
type: float
112+
default: 1.0
113+
min: 0.02
114+
max: 5.0
115+
unit: s
116+
decimal: 2

0 commit comments

Comments
 (0)