2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
23 #include "build/build_config.h"
25 #include "build/debug.h"
27 #include "common/axis.h"
29 #include "config/feature.h"
30 #include "config/parameter_group.h"
31 #include "config/parameter_group_ids.h"
33 #include "drivers/time.h"
35 #include "io/beeper.h"
37 #include "fc/fc_core.h"
38 #include "fc/config.h"
39 #include "fc/rc_controls.h"
40 #include "fc/rc_modes.h"
41 #include "fc/runtime_config.h"
42 #include "fc/controlrate_profile.h"
44 #include "flight/failsafe.h"
45 #include "flight/mixer.h"
46 #include "flight/pid.h"
48 #include "navigation/navigation.h"
52 #include "sensors/sensors.h"
57 * failsafeInit() and failsafeReset() must be called before the other methods are used.
59 * failsafeInit() and failsafeReset() can be called in any order.
60 * failsafeInit() should only be called once.
62 * enable() should be called after system initialisation.
65 static failsafeState_t failsafeState
;
67 PG_REGISTER_WITH_RESET_TEMPLATE(failsafeConfig_t
, failsafeConfig
, PG_FAILSAFE_CONFIG
, 1);
69 PG_RESET_TEMPLATE(failsafeConfig_t
, failsafeConfig
,
70 .failsafe_delay
= 5, // 0.5 sec
71 .failsafe_recovery_delay
= 5, // 0.5 seconds (plus 200ms explicit delay)
72 .failsafe_off_delay
= 200, // 20sec
73 .failsafe_throttle
= 1000, // default throttle off.
74 .failsafe_throttle_low_delay
= 0, // default throttle low delay for "just disarm" on failsafe condition
75 .failsafe_procedure
= FAILSAFE_PROCEDURE_AUTO_LANDING
, // default full failsafe procedure
76 .failsafe_fw_roll_angle
= -200, // 20 deg left
77 .failsafe_fw_pitch_angle
= 100, // 10 deg dive (yes, positive means dive)
78 .failsafe_fw_yaw_rate
= -45, // 45 deg/s left yaw (left is negative, 8s for full turn)
79 .failsafe_stick_motion_threshold
= 50,
80 .failsafe_min_distance
= 0, // No minimum distance for failsafe by default
81 .failsafe_min_distance_procedure
= FAILSAFE_PROCEDURE_DROP_IT
, // default minimum distance failsafe procedure
82 .failsafe_mission
= true, // Enable failsafe in WP mode or not
86 FAILSAFE_CHANNEL_HOLD
, // Hold last known good value
87 FAILSAFE_CHANNEL_NEUTRAL
, // RPY = zero, THR = zero
88 FAILSAFE_CHANNEL_AUTO
, // Defined by failsafe configured values
89 } failsafeChannelBehavior_e
;
92 bool bypassNavigation
;
94 failsafeChannelBehavior_e channelBehavior
[4];
95 } failsafeProcedureLogic_t
;
97 static const failsafeProcedureLogic_t failsafeProcedureLogic
[] = {
98 [FAILSAFE_PROCEDURE_AUTO_LANDING
] = {
99 .bypassNavigation
= true,
100 .forceAngleMode
= true,
102 FAILSAFE_CHANNEL_AUTO
, // ROLL
103 FAILSAFE_CHANNEL_AUTO
, // PITCH
104 FAILSAFE_CHANNEL_AUTO
, // YAW
105 FAILSAFE_CHANNEL_AUTO
// THROTTLE
109 [FAILSAFE_PROCEDURE_DROP_IT
] = {
110 .bypassNavigation
= true,
111 .forceAngleMode
= true,
113 FAILSAFE_CHANNEL_NEUTRAL
, // ROLL
114 FAILSAFE_CHANNEL_NEUTRAL
, // PITCH
115 FAILSAFE_CHANNEL_NEUTRAL
, // YAW
116 FAILSAFE_CHANNEL_NEUTRAL
// THROTTLE
120 [FAILSAFE_PROCEDURE_RTH
] = {
121 .bypassNavigation
= false,
122 .forceAngleMode
= true,
124 FAILSAFE_CHANNEL_NEUTRAL
, // ROLL
125 FAILSAFE_CHANNEL_NEUTRAL
, // PITCH
126 FAILSAFE_CHANNEL_NEUTRAL
, // YAW
127 FAILSAFE_CHANNEL_HOLD
// THROTTLE
131 [FAILSAFE_PROCEDURE_NONE
] = {
132 .bypassNavigation
= false,
133 .forceAngleMode
= false,
135 FAILSAFE_CHANNEL_HOLD
, // ROLL
136 FAILSAFE_CHANNEL_HOLD
, // PITCH
137 FAILSAFE_CHANNEL_HOLD
, // YAW
138 FAILSAFE_CHANNEL_HOLD
// THROTTLE
144 * Should called when the failsafe config needs to be changed - e.g. a different profile has been selected.
146 void failsafeReset(void)
148 failsafeState
.rxDataFailurePeriod
= PERIOD_RXDATA_FAILURE
+ failsafeConfig()->failsafe_delay
* MILLIS_PER_TENTH_SECOND
;
149 failsafeState
.rxDataRecoveryPeriod
= PERIOD_RXDATA_RECOVERY
+ failsafeConfig()->failsafe_recovery_delay
* MILLIS_PER_TENTH_SECOND
;
150 failsafeState
.validRxDataReceivedAt
= 0;
151 failsafeState
.validRxDataFailedAt
= 0;
152 failsafeState
.throttleLowPeriod
= 0;
153 failsafeState
.landingShouldBeFinishedAt
= 0;
154 failsafeState
.receivingRxDataPeriod
= 0;
155 failsafeState
.receivingRxDataPeriodPreset
= 0;
156 failsafeState
.phase
= FAILSAFE_IDLE
;
157 failsafeState
.rxLinkState
= FAILSAFE_RXLINK_DOWN
;
158 failsafeState
.activeProcedure
= failsafeConfig()->failsafe_procedure
;
160 failsafeState
.lastGoodRcCommand
[ROLL
] = 0;
161 failsafeState
.lastGoodRcCommand
[PITCH
] = 0;
162 failsafeState
.lastGoodRcCommand
[YAW
] = 0;
163 failsafeState
.lastGoodRcCommand
[THROTTLE
] = 1000;
166 void failsafeInit(void)
168 failsafeState
.events
= 0;
169 failsafeState
.monitoring
= false;
170 failsafeState
.suspended
= false;
174 bool failsafeBypassNavigation(void)
176 return failsafeState
.active
&&
177 failsafeState
.controlling
&&
178 failsafeProcedureLogic
[failsafeState
.activeProcedure
].bypassNavigation
;
181 bool failsafeMayRequireNavigationMode(void)
183 return (failsafeConfig()->failsafe_procedure
== FAILSAFE_PROCEDURE_RTH
) ||
184 (failsafeConfig()->failsafe_min_distance_procedure
== FAILSAFE_PROCEDURE_RTH
);
188 failsafePhase_e
failsafePhase(void)
190 return failsafeState
.phase
;
193 bool failsafeIsMonitoring(void)
195 return failsafeState
.monitoring
;
198 bool failsafeIsActive(void)
200 return failsafeState
.active
;
203 bool failsafeShouldApplyControlInput(void)
205 return failsafeState
.controlling
;
208 bool failsafeRequiresAngleMode(void)
210 return failsafeState
.active
&&
211 failsafeState
.controlling
&&
212 failsafeProcedureLogic
[failsafeState
.activeProcedure
].forceAngleMode
;
215 bool failsafeRequiresMotorStop(void)
217 return failsafeState
.active
&&
218 failsafeState
.activeProcedure
== FAILSAFE_PROCEDURE_AUTO_LANDING
&&
219 failsafeConfig()->failsafe_throttle
< getThrottleIdleValue();
222 void failsafeStartMonitoring(void)
224 failsafeState
.monitoring
= true;
227 static bool failsafeShouldHaveCausedLandingByNow(void)
229 return failsafeConfig()->failsafe_off_delay
&& (millis() > failsafeState
.landingShouldBeFinishedAt
);
232 static void failsafeSetActiveProcedure(failsafeProcedure_e procedure
)
234 failsafeState
.activeProcedure
= procedure
;
237 static void failsafeActivate(failsafePhase_e newPhase
)
239 failsafeState
.active
= true;
240 failsafeState
.controlling
= true;
241 failsafeState
.phase
= newPhase
;
242 ENABLE_FLIGHT_MODE(FAILSAFE_MODE
);
243 failsafeState
.landingShouldBeFinishedAt
= millis() + failsafeConfig()->failsafe_off_delay
* MILLIS_PER_TENTH_SECOND
;
245 failsafeState
.events
++;
248 void failsafeUpdateRcCommandValues(void)
250 if (!failsafeState
.active
) {
251 for (int idx
= 0; idx
< 4; idx
++) {
252 failsafeState
.lastGoodRcCommand
[idx
] = rcCommand
[idx
];
257 void failsafeApplyControlInput(void)
259 // Prepare FAILSAFE_CHANNEL_AUTO values for rcCommand
260 int16_t autoRcCommand
[4];
261 if (STATE(FIXED_WING_LEGACY
)) {
262 autoRcCommand
[ROLL
] = pidAngleToRcCommand(failsafeConfig()->failsafe_fw_roll_angle
, pidProfile()->max_angle_inclination
[FD_ROLL
]);
263 autoRcCommand
[PITCH
] = pidAngleToRcCommand(failsafeConfig()->failsafe_fw_pitch_angle
, pidProfile()->max_angle_inclination
[FD_PITCH
]);
264 autoRcCommand
[YAW
] = -pidRateToRcCommand(failsafeConfig()->failsafe_fw_yaw_rate
, currentControlRateProfile
->stabilized
.rates
[FD_YAW
]);
265 autoRcCommand
[THROTTLE
] = failsafeConfig()->failsafe_throttle
;
268 for (int i
= 0; i
< 3; i
++) {
269 autoRcCommand
[i
] = 0;
271 autoRcCommand
[THROTTLE
] = failsafeConfig()->failsafe_throttle
;
274 // Apply channel values
275 for (int idx
= 0; idx
< 4; idx
++) {
276 switch (failsafeProcedureLogic
[failsafeState
.activeProcedure
].channelBehavior
[idx
]) {
277 case FAILSAFE_CHANNEL_HOLD
:
278 rcCommand
[idx
] = failsafeState
.lastGoodRcCommand
[idx
];
281 case FAILSAFE_CHANNEL_NEUTRAL
:
290 rcCommand
[idx
] = feature(FEATURE_REVERSIBLE_MOTORS
) ? PWM_RANGE_MIDDLE
: getThrottleIdleValue();
295 case FAILSAFE_CHANNEL_AUTO
:
296 rcCommand
[idx
] = autoRcCommand
[idx
];
302 bool failsafeIsReceivingRxData(void)
304 return (failsafeState
.rxLinkState
== FAILSAFE_RXLINK_UP
);
307 void failsafeOnRxSuspend(void)
309 failsafeState
.suspended
= true;
312 bool failsafeIsSuspended(void)
314 return failsafeState
.suspended
;
317 void failsafeOnRxResume(void)
319 failsafeState
.suspended
= false; // restart monitoring
320 failsafeState
.validRxDataReceivedAt
= millis(); // prevent RX link down trigger, restart rx link up
321 failsafeState
.rxLinkState
= FAILSAFE_RXLINK_UP
; // do so while rx link is up
324 void failsafeOnValidDataReceived(void)
326 failsafeState
.validRxDataReceivedAt
= millis();
327 if ((failsafeState
.validRxDataReceivedAt
- failsafeState
.validRxDataFailedAt
) > failsafeState
.rxDataRecoveryPeriod
) {
328 failsafeState
.rxLinkState
= FAILSAFE_RXLINK_UP
;
332 void failsafeOnValidDataFailed(void)
334 failsafeState
.validRxDataFailedAt
= millis();
335 if ((failsafeState
.validRxDataFailedAt
- failsafeState
.validRxDataReceivedAt
) > failsafeState
.rxDataFailurePeriod
) {
336 failsafeState
.rxLinkState
= FAILSAFE_RXLINK_DOWN
;
340 static bool failsafeCheckStickMotion(void)
342 if (failsafeConfig()->failsafe_stick_motion_threshold
> 0) {
343 uint32_t totalRcDelta
= 0;
345 totalRcDelta
+= ABS(rxGetChannelValue(ROLL
) - PWM_RANGE_MIDDLE
);
346 totalRcDelta
+= ABS(rxGetChannelValue(PITCH
) - PWM_RANGE_MIDDLE
);
347 totalRcDelta
+= ABS(rxGetChannelValue(YAW
) - PWM_RANGE_MIDDLE
);
349 return totalRcDelta
>= failsafeConfig()->failsafe_stick_motion_threshold
;
356 static failsafeProcedure_e
failsafeChooseFailsafeProcedure(void)
358 if (FLIGHT_MODE(NAV_WP_MODE
) && !failsafeConfig()->failsafe_mission
) {
359 return FAILSAFE_PROCEDURE_NONE
;
362 // Craft is closer than minimum failsafe procedure distance (if set to non-zero)
363 // GPS must also be working, and home position set
364 if ((failsafeConfig()->failsafe_min_distance
> 0) &&
365 ((GPS_distanceToHome
* 100) < failsafeConfig()->failsafe_min_distance
) &&
366 sensors(SENSOR_GPS
) && STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
)) {
368 // Use the alternate, minimum distance failsafe procedure instead
369 return failsafeConfig()->failsafe_min_distance_procedure
;
372 return failsafeConfig()->failsafe_procedure
;
375 void failsafeUpdateState(void)
377 if (!failsafeIsMonitoring() || failsafeIsSuspended()) {
381 const bool receivingRxDataAndNotFailsafeMode
= failsafeIsReceivingRxData() && !IS_RC_MODE_ACTIVE(BOXFAILSAFE
);
382 const bool armed
= ARMING_FLAG(ARMED
);
383 const bool sticksAreMoving
= failsafeCheckStickMotion();
384 beeperMode_e beeperMode
= BEEPER_SILENCE
;
386 // Beep RX lost only if we are not seeing data and we have been armed earlier
387 if (!receivingRxDataAndNotFailsafeMode
&& ARMING_FLAG(WAS_EVER_ARMED
)) {
388 beeperMode
= BEEPER_RX_LOST
;
394 reprocessState
= false;
396 switch (failsafeState
.phase
) {
399 // Track throttle command below minimum time
400 if (THROTTLE_HIGH
== calculateThrottleStatus(THROTTLE_STATUS_TYPE_RC
)) {
401 failsafeState
.throttleLowPeriod
= millis() + failsafeConfig()->failsafe_throttle_low_delay
* MILLIS_PER_TENTH_SECOND
;
403 if (!receivingRxDataAndNotFailsafeMode
) {
404 if ((failsafeConfig()->failsafe_throttle_low_delay
&& (millis() > failsafeState
.throttleLowPeriod
)) || STATE(NAV_MOTOR_STOP_OR_IDLE
)) {
405 // JustDisarm: throttle was LOW for at least 'failsafe_throttle_low_delay' seconds or waiting for launch
406 // Don't disarm at all if `failsafe_throttle_low_delay` is set to zero
407 failsafeSetActiveProcedure(FAILSAFE_PROCEDURE_DROP_IT
);
408 failsafeActivate(FAILSAFE_LANDED
); // skip auto-landing procedure
409 failsafeState
.receivingRxDataPeriodPreset
= PERIOD_OF_3_SECONDS
; // require 3 seconds of valid rxData
411 failsafeState
.phase
= FAILSAFE_RX_LOSS_DETECTED
;
413 reprocessState
= true;
416 // When NOT armed, show rxLinkState of failsafe switch in GUI (failsafe mode)
417 if (!receivingRxDataAndNotFailsafeMode
) {
418 ENABLE_FLIGHT_MODE(FAILSAFE_MODE
);
420 DISABLE_FLIGHT_MODE(FAILSAFE_MODE
);
422 // Throttle low period expired (= low long enough for JustDisarm)
423 failsafeState
.throttleLowPeriod
= 0;
427 case FAILSAFE_RX_LOSS_DETECTED
:
428 if (receivingRxDataAndNotFailsafeMode
) {
429 failsafeState
.phase
= FAILSAFE_RX_LOSS_RECOVERED
;
431 // Set active failsafe procedure
432 failsafeSetActiveProcedure(failsafeChooseFailsafeProcedure());
434 switch (failsafeState
.activeProcedure
) {
435 case FAILSAFE_PROCEDURE_AUTO_LANDING
:
436 // Stabilize, and set Throttle to specified level
437 failsafeActivate(FAILSAFE_LANDING
);
440 case FAILSAFE_PROCEDURE_DROP_IT
:
442 failsafeActivate(FAILSAFE_LANDED
); // skip auto-landing procedure
443 failsafeState
.receivingRxDataPeriodPreset
= PERIOD_OF_3_SECONDS
; // require 3 seconds of valid rxData
447 case FAILSAFE_PROCEDURE_RTH
:
448 // Proceed to handling & monitoring RTH navigation
449 failsafeActivate(FAILSAFE_RETURN_TO_HOME
);
453 case FAILSAFE_PROCEDURE_NONE
:
455 // Do nothing procedure
456 failsafeActivate(FAILSAFE_RX_LOSS_IDLE
);
460 reprocessState
= true;
463 /* A very simple do-nothing failsafe procedure. The only thing it will do is monitor the receiver state and switch out of FAILSAFE condition */
464 case FAILSAFE_RX_LOSS_IDLE
:
465 if (receivingRxDataAndNotFailsafeMode
&& sticksAreMoving
) {
466 failsafeState
.phase
= FAILSAFE_RX_LOSS_RECOVERED
;
467 reprocessState
= true;
472 case FAILSAFE_RETURN_TO_HOME
:
473 if (receivingRxDataAndNotFailsafeMode
&& sticksAreMoving
) {
475 failsafeState
.phase
= FAILSAFE_RX_LOSS_RECOVERED
;
476 reprocessState
= true;
480 beeperMode
= BEEPER_RX_LOST_LANDING
;
482 bool rthLanded
= false;
483 switch (getStateOfForcedRTH()) {
484 case RTH_IN_PROGRESS
:
493 // This shouldn't happen. If RTH was somehow aborted during failsafe - fallback to FAILSAFE_LANDING procedure
495 failsafeSetActiveProcedure(FAILSAFE_PROCEDURE_AUTO_LANDING
);
496 failsafeActivate(FAILSAFE_LANDING
);
497 reprocessState
= true;
500 if (rthLanded
|| !armed
) {
501 failsafeState
.receivingRxDataPeriodPreset
= PERIOD_OF_30_SECONDS
; // require 30 seconds of valid rxData
502 failsafeState
.phase
= FAILSAFE_LANDED
;
503 reprocessState
= true;
509 case FAILSAFE_LANDING
:
510 if (receivingRxDataAndNotFailsafeMode
&& sticksAreMoving
) {
511 failsafeState
.phase
= FAILSAFE_RX_LOSS_RECOVERED
;
512 reprocessState
= true;
515 beeperMode
= BEEPER_RX_LOST_LANDING
;
517 if (failsafeShouldHaveCausedLandingByNow() || !armed
) {
518 failsafeState
.receivingRxDataPeriodPreset
= PERIOD_OF_30_SECONDS
; // require 30 seconds of valid rxData
519 failsafeState
.phase
= FAILSAFE_LANDED
;
520 reprocessState
= true;
524 case FAILSAFE_LANDED
:
525 ENABLE_ARMING_FLAG(ARMING_DISABLED_FAILSAFE_SYSTEM
); // To prevent accidently rearming by an intermittent rx link
526 disarm(DISARM_FAILSAFE
);
527 failsafeState
.receivingRxDataPeriod
= millis() + failsafeState
.receivingRxDataPeriodPreset
; // set required period of valid rxData
528 failsafeState
.phase
= FAILSAFE_RX_LOSS_MONITORING
;
529 failsafeState
.controlling
= false; // Failsafe no longer in control of the machine - release control to pilot
530 reprocessState
= true;
533 case FAILSAFE_RX_LOSS_MONITORING
:
534 // Monitoring the rx link to allow rearming when it has become good for > `receivingRxDataPeriodPreset` time.
535 if (receivingRxDataAndNotFailsafeMode
) {
536 if (millis() > failsafeState
.receivingRxDataPeriod
) {
537 // rx link is good now, when arming via ARM switch, it must be OFF first
538 if (!IS_RC_MODE_ACTIVE(BOXARM
)) {
539 // XXX: Requirements for removing the ARMING_DISABLED_FAILSAFE_SYSTEM flag
540 // are tested by osd.c to show the user how to re-arm. If these
541 // requirements change, update osdArmingDisabledReasonMessage().
542 DISABLE_ARMING_FLAG(ARMING_DISABLED_FAILSAFE_SYSTEM
);
543 failsafeState
.phase
= FAILSAFE_RX_LOSS_RECOVERED
;
544 reprocessState
= true;
548 failsafeState
.receivingRxDataPeriod
= millis() + failsafeState
.receivingRxDataPeriodPreset
;
552 case FAILSAFE_RX_LOSS_RECOVERED
:
553 // Entering IDLE with the requirement that throttle first must be at min_check for failsafe_throttle_low_delay period.
554 // This is to prevent that JustDisarm is activated on the next iteration.
555 // Because that would have the effect of shutting down failsafe handling on intermittent connections.
556 failsafeState
.throttleLowPeriod
= millis() + failsafeConfig()->failsafe_throttle_low_delay
* MILLIS_PER_TENTH_SECOND
;
557 failsafeState
.phase
= FAILSAFE_IDLE
;
558 failsafeState
.active
= false;
559 failsafeState
.controlling
= false;
560 DISABLE_FLIGHT_MODE(FAILSAFE_MODE
);
561 reprocessState
= true;
567 } while (reprocessState
);
569 if (beeperMode
!= BEEPER_SILENCE
) {