2 * This file is part of Betaflight.
4 * Betaflight 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 * Betaflight 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 Betaflight. If not, see <http://www.gnu.org/licenses/>.
26 #include "build/debug.h"
28 #include "common/axis.h"
29 #include "common/filter.h"
30 #include "common/maths.h"
31 #include "common/utils.h"
33 #include "drivers/time.h"
37 #include "config/config.h"
39 #include "fc/rc_controls.h"
40 #include "fc/rc_modes.h"
41 #include "fc/runtime_config.h"
43 #include "flight/failsafe.h"
44 #include "flight/imu.h"
45 #include "flight/pid.h"
46 #include "flight/position.h"
50 #include "sensors/acceleration.h"
52 #include "gps_rescue.h"
72 RESCUE_CRASH_FLIP_DETECTED
,
76 } rescueFailureState_e
;
80 float returnAltitudeCm
;
81 float targetAltitudeCm
;
82 float targetLandingAltitudeCm
;
83 float targetVelocityCmS
;
84 float pitchAngleLimitDeg
;
85 float rollAngleLimitDeg
;
86 float descentDistanceM
;
87 int8_t secondsFailing
;
89 float descentRateModifier
;
91 float disarmThreshold
;
92 float velocityITermAccumulator
;
93 float velocityPidCutoff
;
94 float velocityPidCutoffModifier
;
95 float proximityToLandingArea
;
96 float velocityItermRelax
;
100 float currentAltitudeCm
;
101 float distanceToHomeCm
;
102 float distanceToHomeM
;
103 uint16_t groundSpeedCmS
;
104 int16_t directionToHome
;
108 float gpsDataIntervalSeconds
;
109 float altitudeDataIntervalSeconds
;
110 float gpsRescueTaskIntervalSeconds
;
111 float velocityToHomeCmS
;
112 float alitutudeStepCm
;
115 } rescueSensorData_s
;
119 rescueFailureState_e failure
;
120 rescueSensorData_s sensor
;
121 rescueIntent_s intent
;
125 #define GPS_RESCUE_MAX_YAW_RATE 180 // deg/sec max yaw rate
126 #define GPS_RESCUE_MIN_DESCENT_DIST_M 5 // minimum descent distance
127 #define GPS_RESCUE_MAX_THROTTLE_ITERM 200 // max iterm value for throttle in degrees * 100
129 static float rescueThrottle
;
130 static float rescueYaw
;
131 float gpsRescueAngle
[ANGLE_INDEX_COUNT
] = { 0, 0 };
132 bool magForceDisable
= false;
133 static bool newGPSData
= false;
134 static pt2Filter_t throttleDLpf
;
135 static pt1Filter_t velocityDLpf
;
136 static pt3Filter_t velocityUpsampleLpf
;
138 rescueState_s rescueState
;
140 void gpsRescueInit(void)
142 rescueState
.sensor
.gpsRescueTaskIntervalSeconds
= HZ_TO_INTERVAL(TASK_GPS_RESCUE_RATE_HZ
);
144 float cutoffHz
, gain
;
145 cutoffHz
= positionConfig()->altitude_d_lpf
/ 100.0f
;
146 gain
= pt2FilterGain(cutoffHz
, rescueState
.sensor
.gpsRescueTaskIntervalSeconds
);
147 pt2FilterInit(&throttleDLpf
, gain
);
149 cutoffHz
= gpsRescueConfig()->pitchCutoffHz
/ 100.0f
;
150 rescueState
.intent
.velocityPidCutoff
= cutoffHz
;
151 rescueState
.intent
.velocityPidCutoffModifier
= 1.0f
;
152 gain
= pt1FilterGain(cutoffHz
, 1.0f
);
153 pt1FilterInit(&velocityDLpf
, gain
);
156 gain
= pt3FilterGain(cutoffHz
, rescueState
.sensor
.gpsRescueTaskIntervalSeconds
);
157 pt3FilterInit(&velocityUpsampleLpf
, gain
);
161 If we have new GPS data, update home heading if possible and applicable.
163 void gpsRescueNewGpsData(void)
168 static void rescueStart(void)
170 rescueState
.phase
= RESCUE_INITIALIZE
;
173 static void rescueStop(void)
175 rescueState
.phase
= RESCUE_IDLE
;
178 // Things that need to run when GPS Rescue is enabled, and while armed, but while there is no Rescue in place
179 static void setReturnAltitude(void)
181 // Hold maxAltitude at zero while disarmed, but if set_home_point_once is true, hold maxAlt until power cycled
182 if (!ARMING_FLAG(ARMED
) && !gpsConfig()->gps_set_home_point_once
) {
183 rescueState
.intent
.maxAltitudeCm
= 0.0f
;
187 // While armed, but not during the rescue, update the max altitude value
188 rescueState
.intent
.maxAltitudeCm
= fmaxf(rescueState
.sensor
.currentAltitudeCm
, rescueState
.intent
.maxAltitudeCm
);
191 // set the target altitude to current values, so there will be no D kick on first run
192 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
194 // Keep the descent distance and intended altitude up to date with latest GPS values
195 rescueState
.intent
.descentDistanceM
= constrainf(rescueState
.sensor
.distanceToHomeM
, GPS_RESCUE_MIN_DESCENT_DIST_M
, gpsRescueConfig()->descentDistanceM
);
196 const float initialAltitudeCm
= gpsRescueConfig()->initialAltitudeM
* 100.0f
;
197 const float rescueAltitudeBufferCm
= gpsRescueConfig()->rescueAltitudeBufferM
* 100.0f
;
198 switch (gpsRescueConfig()->altitudeMode
) {
199 case GPS_RESCUE_ALT_MODE_FIXED
:
200 rescueState
.intent
.returnAltitudeCm
= initialAltitudeCm
;
202 case GPS_RESCUE_ALT_MODE_CURRENT
:
203 rescueState
.intent
.returnAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueAltitudeBufferCm
;
205 case GPS_RESCUE_ALT_MODE_MAX
:
207 rescueState
.intent
.returnAltitudeCm
= rescueState
.intent
.maxAltitudeCm
+ rescueAltitudeBufferCm
;
213 static void rescueAttainPosition(void)
215 // runs at 100hz, but only updates RPYT settings when new GPS Data arrives and when not in idle phase.
216 static float previousVelocityError
= 0.0f
;
217 static float velocityI
= 0.0f
;
218 static float throttleI
= 0.0f
;
219 static float previousAltitudeError
= 0.0f
;
220 static int16_t throttleAdjustment
= 0;
222 switch (rescueState
.phase
) {
224 // values to be returned when no rescue is active
225 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
226 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
227 rescueThrottle
= rcCommand
[THROTTLE
];
229 case RESCUE_INITIALIZE
:
230 // Initialize internal variables each time GPS Rescue is started
231 previousVelocityError
= 0.0f
;
234 previousAltitudeError
= 0.0f
;
235 throttleAdjustment
= 0;
236 rescueState
.intent
.disarmThreshold
= gpsRescueConfig()->disarmThreshold
/ 10.0f
;
238 case RESCUE_DO_NOTHING
:
239 // 20s of slow descent for switch induced sanity failures to allow time to recover
240 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
241 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
242 rescueThrottle
= gpsRescueConfig()->throttleHover
- 100;
249 Altitude (throttle) controller
251 // currentAltitudeCm is updated at TASK_GPS_RESCUE_RATE_HZ
252 const float altitudeError
= (rescueState
.intent
.targetAltitudeCm
- rescueState
.sensor
.currentAltitudeCm
) * 0.01f
;
253 // height above target in metres (negative means too low)
254 // at the start, the target starts at current altitude plus one step. Increases stepwise to intended value.
257 const float throttleP
= gpsRescueConfig()->throttleP
* altitudeError
;
260 throttleI
+= 0.1f
* gpsRescueConfig()->throttleI
* altitudeError
* rescueState
.sensor
.altitudeDataIntervalSeconds
;
261 throttleI
= constrainf(throttleI
, -1.0f
* GPS_RESCUE_MAX_THROTTLE_ITERM
, 1.0f
* GPS_RESCUE_MAX_THROTTLE_ITERM
);
262 // up to 20% increase in throttle from I alone
264 // D component is error based, so includes positive boost when climbing and negative boost on descent
265 float verticalSpeed
= ((altitudeError
- previousAltitudeError
) / rescueState
.sensor
.altitudeDataIntervalSeconds
);
266 previousAltitudeError
= altitudeError
;
267 verticalSpeed
+= rescueState
.intent
.descentRateModifier
* verticalSpeed
;
268 // add up to 2x D when descent rate is faster
270 float throttleD
= pt2FilterApply(&throttleDLpf
, verticalSpeed
);
272 throttleD
= gpsRescueConfig()->throttleD
* throttleD
;
274 // acceleration component not currently implemented - was needed previously due to GPS lag, maybe not needed now.
276 float tiltAdjustment
= 1.0f
- getCosTiltAngle(); // 0 = flat, gets to 0.2 correcting on a windy day
277 tiltAdjustment
*= (gpsRescueConfig()->throttleHover
- 1000);
278 // if hover is 1300, and adjustment .2, this gives us 0.2*300 or 60 of extra throttle, not much, but useful
279 // too much and landings with lots of pitch adjustment, eg windy days, can be a problem
281 throttleAdjustment
= throttleP
+ throttleI
+ throttleD
+ tiltAdjustment
;
283 rescueThrottle
= gpsRescueConfig()->throttleHover
+ throttleAdjustment
;
284 rescueThrottle
= constrainf(rescueThrottle
, gpsRescueConfig()->throttleMin
, gpsRescueConfig()->throttleMax
);
285 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 0, lrintf(throttleP
));
286 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 1, lrintf(throttleD
));
289 Heading / yaw controller
291 // simple yaw P controller with roll mixed in.
292 // attitude.values.yaw is set by imuCalculateEstimatedAttitude() and is updated from GPS while groundspeed exceeds 2 m/s
293 // below 2m/s groundspeed, the IMU uses gyro to estimate yaw attitude change from previous values
294 // above 2m/s, GPS course over ground us ysed to 'correct' the IMU heading
295 // if the course over ground, due to wind or pre-exiting movement, is different from the attitude of the quad, the GPS correction will be less accurate
296 // the craft should not return much less than 5m/s during the rescue or the GPS corrections may be inaccurate.
297 // the faster the return speed, the more accurate the IMU will be, but the consequences of IMU error at the start are greater
298 // A compass (magnetometer) is vital for accurate GPS rescue at slow speeds, but must be calibrated and validated
299 // WARNING: Some GPS units give false Home values! Always check the arrow points to home on leaving home.
300 rescueYaw
= rescueState
.sensor
.errorAngle
* gpsRescueConfig()->yawP
* rescueState
.intent
.yawAttenuator
* 0.1f
;
301 rescueYaw
= constrainf(rescueYaw
, -GPS_RESCUE_MAX_YAW_RATE
, GPS_RESCUE_MAX_YAW_RATE
);
302 // rescueYaw is the yaw rate in deg/s to correct the heading error
304 // *** mix in some roll. very important for heading tracking, since a yaw rate means the quad has drifted sideways
305 const float rollMixAttenuator
= constrainf(1.0f
- fabsf(rescueYaw
) * 0.01f
, 0.0f
, 1.0f
);
306 // less roll at higher yaw rates, no roll at 100 deg/s of yaw
307 const float rollAdjustment
= -rescueYaw
* gpsRescueConfig()->rollMix
* rollMixAttenuator
;
308 // if rollMix = 100, the roll:yaw ratio is 1:1 at small angles, reducing linearly to zero when the yaw rate is 100 deg/s
309 // when gpsRescueConfig()->rollMix is zero, there is no roll adjustment
310 // rollAdjustment is degrees * 100
311 // note that the roll element has the opposite sign to the yaw element *before* GET_DIRECTION
312 const float rollLimit
= 100.0f
* rescueState
.intent
.rollAngleLimitDeg
;
313 gpsRescueAngle
[AI_ROLL
] = constrainf(rollAdjustment
, -rollLimit
, rollLimit
);
314 // gpsRescueAngle is added to the normal roll Angle Mode corrections in pid.c
316 rescueYaw
*= GET_DIRECTION(rcControlsConfig()->yaw_control_reversed
);
317 // rescueYaw is the yaw rate in deg/s to correct the heading error
320 Pitch / velocity controller
322 static float pitchAdjustment
= 0.0f
;
325 const float sampleIntervalNormaliseFactor
= rescueState
.sensor
.gpsDataIntervalSeconds
* 10.0f
;
327 const float velocityError
= rescueState
.intent
.targetVelocityCmS
- rescueState
.sensor
.velocityToHomeCmS
;
328 // velocityError is in cm per second, positive means too slow.
329 // NB positive pitch setpoint means nose down.
330 // target velocity can be very negative leading to large error before the start, with overshoot
333 const float velocityP
= velocityError
* gpsRescueConfig()->velP
;
336 velocityI
+= 0.01f
* gpsRescueConfig()->velI
* velocityError
* sampleIntervalNormaliseFactor
* rescueState
.intent
.velocityItermRelax
;
337 // velocityItermRelax is a time-based factor, 0->1 with time constant of 1s from when we start to fly home
338 // avoids excess iTerm during the initial acceleration phase.
339 velocityI
*= rescueState
.intent
.proximityToLandingArea
;
340 // reduce iTerm sharply when velocity decreases in landing phase, to minimise overshoot during deceleration
342 const float pitchAngleLimit
= rescueState
.intent
.pitchAngleLimitDeg
* 100.0f
;
343 const float velocityPILimit
= 0.5f
* pitchAngleLimit
;
344 velocityI
= constrainf(velocityI
, -velocityPILimit
, velocityPILimit
);
345 // I component alone cannot exceed half the max pitch angle
348 float velocityD
= ((velocityError
- previousVelocityError
) / sampleIntervalNormaliseFactor
);
349 previousVelocityError
= velocityError
;
350 velocityD
*= gpsRescueConfig()->velD
;
352 // smooth the D steps
353 const float cutoffHz
= rescueState
.intent
.velocityPidCutoff
* rescueState
.intent
.velocityPidCutoffModifier
;
354 // note that this cutoff is increased up to 2x as we get closer to landing point in descend()
355 const float gain
= pt1FilterGain(cutoffHz
, rescueState
.sensor
.gpsDataIntervalSeconds
);
356 pt1FilterUpdateCutoff(&velocityDLpf
, gain
);
357 velocityD
= pt1FilterApply(&velocityDLpf
, velocityD
);
359 pitchAdjustment
= velocityP
+ velocityI
+ velocityD
;
360 pitchAdjustment
= constrainf(pitchAdjustment
, -pitchAngleLimit
, pitchAngleLimit
);
361 // limit to maximum allowed angle
363 // pitchAdjustment is the absolute Pitch angle adjustment value in degrees * 100
364 // it gets added to the normal level mode Pitch adjustments in pid.c
365 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 0, lrintf(velocityP
));
366 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 1, lrintf(velocityD
));
369 // upsampling and smoothing of pitch angle steps
370 float pitchAdjustmentFiltered
= pt3FilterApply(&velocityUpsampleLpf
, pitchAdjustment
);
373 gpsRescueAngle
[AI_PITCH
] = pitchAdjustmentFiltered
;
374 // this angle gets added to the normal pitch Angle Mode control values in pid.c - will be seen in pitch setpoint
376 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 3, lrintf(rescueState
.intent
.targetVelocityCmS
));
377 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 1, lrintf(rescueState
.intent
.targetVelocityCmS
));
380 static void performSanityChecks(void)
382 static timeUs_t previousTimeUs
= 0; // Last time Stalled/LowSat was checked
383 static float prevAltitudeCm
= 0.0f
; // to calculate ascent or descent change
384 static float prevTargetAltitudeCm
= 0.0f
; // to calculate ascent or descent target change
385 static float previousDistanceToHomeCm
= 0.0f
; // to check that we are returning
386 static int8_t secondsLowSats
= 0; // Minimum sat detection
387 static int8_t secondsDoingNothing
; // Limit on doing nothing
388 const timeUs_t currentTimeUs
= micros();
390 if (rescueState
.phase
== RESCUE_IDLE
) {
391 rescueState
.failure
= RESCUE_HEALTHY
;
393 } else if (rescueState
.phase
== RESCUE_INITIALIZE
) {
394 // Initialize these variables each time a GPS Rescue is started
395 previousTimeUs
= currentTimeUs
;
396 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
397 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
398 previousDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
400 secondsDoingNothing
= 0;
403 // Handle events that set a failure mode to other than healthy.
404 // Disarm via Abort when sanity on, or for hard Rx loss in FS_ONLY mode
405 // Otherwise allow 20s of semi-controlled descent with impact disarm detection
406 const bool hardFailsafe
= !rxIsReceivingSignal();
408 if (rescueState
.failure
!= RESCUE_HEALTHY
) {
409 // Default to 20s semi-controlled descent with impact detection, then abort
410 rescueState
.phase
= RESCUE_DO_NOTHING
;
412 switch(gpsRescueConfig()->sanityChecks
) {
413 case RESCUE_SANITY_ON
:
414 rescueState
.phase
= RESCUE_ABORT
;
416 case RESCUE_SANITY_FS_ONLY
:
418 rescueState
.phase
= RESCUE_ABORT
;
422 // even with sanity checks off,
423 // override when Allow Arming without Fix is enabled without GPS_FIX_HOME and no Control link available.
424 if (gpsRescueConfig()->allowArmingWithoutFix
&& !STATE(GPS_FIX_HOME
) && hardFailsafe
) {
425 rescueState
.phase
= RESCUE_ABORT
;
430 // Crash detection is enabled in all rescues. If triggered, immediately disarm.
431 if (crashRecoveryModeActive()) {
432 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
433 disarm(DISARM_REASON_CRASH_PROTECTION
);
437 // Check if GPS comms are healthy
438 // ToDo - check if we have an altitude reading; if we have Baro, we can use Landing mode for controlled descent without GPS
439 if (!rescueState
.sensor
.healthy
) {
440 rescueState
.failure
= RESCUE_GPSLOST
;
443 // Things that should run at a low refresh rate (such as flyaway detection, etc) will be checked at 1Hz
444 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
445 if (dTime
< 1000000) { //1hz
448 previousTimeUs
= currentTimeUs
;
450 // checks that we are getting closer to home.
451 // if the quad is stuck, or if GPS data packets stop, there will be no change in distance to home
452 // we can't use rescueState.sensor.currentVelocity because it will be held at the last good value if GPS data updates stop
453 if (rescueState
.phase
== RESCUE_FLY_HOME
) {
454 const float velocityToHomeCmS
= previousDistanceToHomeCm
- rescueState
.sensor
.distanceToHomeCm
; // cm/s
455 previousDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
456 rescueState
.intent
.secondsFailing
+= (velocityToHomeCmS
< 0.5f
* rescueState
.intent
.targetVelocityCmS
) ? 1 : -1;
457 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 15);
458 if (rescueState
.intent
.secondsFailing
== 15) {
460 //If there is a mag and has not been disabled, we have to assume is healthy and has been used in imu.c
461 if (sensors(SENSOR_MAG
) && gpsRescueConfig()->useMag
&& !magForceDisable
) {
462 //Try again with mag disabled
463 magForceDisable
= true;
464 rescueState
.intent
.secondsFailing
= 0;
468 rescueState
.failure
= RESCUE_FLYAWAY
;
473 secondsLowSats
+= (!STATE(GPS_FIX
) || (gpsSol
.numSat
< GPS_MIN_SAT_COUNT
)) ? 1 : -1;
474 secondsLowSats
= constrain(secondsLowSats
, 0, 10);
476 if (secondsLowSats
== 10) {
477 rescueState
.failure
= RESCUE_LOWSATS
;
481 // These conditions ignore sanity mode settings, and apply in all rescues, to handle getting stuck in a climb or descend
483 const float actualAltitudeChange
= rescueState
.sensor
.currentAltitudeCm
- prevAltitudeCm
;
484 const float targetAltitudeChange
= rescueState
.intent
.targetAltitudeCm
- prevTargetAltitudeCm
;
485 const float ratio
= actualAltitudeChange
/ targetAltitudeChange
;
486 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
487 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
489 switch (rescueState
.phase
) {
491 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
492 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
493 if (rescueState
.intent
.secondsFailing
== 10) {
494 rescueState
.phase
= RESCUE_ABORT
;
495 // Landing mode shouldn't take more than 10s
498 case RESCUE_ATTAIN_ALT
:
500 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
501 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
502 if (rescueState
.intent
.secondsFailing
== 10) {
503 rescueState
.phase
= RESCUE_LANDING
;
504 rescueState
.intent
.secondsFailing
= 0;
505 // if can't climb, or slow descending, enable impact detection and time out in 10s
508 case RESCUE_DO_NOTHING
:
509 secondsDoingNothing
= MIN(secondsDoingNothing
+ 1, 20);
510 if (secondsDoingNothing
== 20) {
511 rescueState
.phase
= RESCUE_ABORT
;
512 // time-limited semi-controlled fall with impact detection
520 DEBUG_SET(DEBUG_RTH
, 2, (rescueState
.failure
* 10 + rescueState
.phase
));
521 DEBUG_SET(DEBUG_RTH
, 3, (rescueState
.intent
.secondsFailing
* 100 + secondsLowSats
));
524 static void sensorUpdate(void)
526 static float prevDistanceToHomeCm
= 0.0f
;
527 const timeUs_t currentTimeUs
= micros();
529 static timeUs_t previousAltitudeDataTimeUs
= 0;
530 const timeDelta_t altitudeDataIntervalUs
= cmpTimeUs(currentTimeUs
, previousAltitudeDataTimeUs
);
531 rescueState
.sensor
.altitudeDataIntervalSeconds
= altitudeDataIntervalUs
* 0.000001f
;
532 previousAltitudeDataTimeUs
= currentTimeUs
;
534 rescueState
.sensor
.currentAltitudeCm
= getAltitude();
536 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 2, lrintf(rescueState
.sensor
.currentAltitudeCm
));
537 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 2, lrintf(rescueState
.sensor
.currentAltitudeCm
));
538 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 0, rescueState
.sensor
.groundSpeedCmS
); // groundspeed cm/s
539 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 1, gpsSol
.groundCourse
); // degrees * 10
540 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 2, attitude
.values
.yaw
); // degrees * 10
541 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 3, rescueState
.sensor
.directionToHome
); // degrees * 10
543 rescueState
.sensor
.healthy
= gpsIsHealthy();
545 if (rescueState
.phase
== RESCUE_LANDING
) {
546 // do this at sensor update rate, not the much slower GPS rate, for quick disarm
547 rescueState
.sensor
.accMagnitude
= (float) sqrtf(sq(acc
.accADC
[Z
] - acc
.dev
.acc_1G
) + sq(acc
.accADC
[X
]) + sq(acc
.accADC
[Y
])) * acc
.dev
.acc_1G_rec
;
548 // Note: subtracting 1G from Z assumes the quad is 'flat' with respect to the horizon. A true non-gravity acceleration value, regardless of attitude, may be better.
551 rescueState
.sensor
.directionToHome
= GPS_directionToHome
;
552 rescueState
.sensor
.errorAngle
= (attitude
.values
.yaw
- rescueState
.sensor
.directionToHome
) * 0.1f
;
553 // both attitude and direction are in degrees * 10, errorAngle is degrees
554 if (rescueState
.sensor
.errorAngle
<= -180) {
555 rescueState
.sensor
.errorAngle
+= 360;
556 } else if (rescueState
.sensor
.errorAngle
> 180) {
557 rescueState
.sensor
.errorAngle
-= 360;
559 rescueState
.sensor
.absErrorAngle
= fabsf(rescueState
.sensor
.errorAngle
);
563 // GPS ground speed, velocity and distance to home will be held at last good values if no new packets
566 rescueState
.sensor
.distanceToHomeCm
= GPS_distanceToHomeCm
;
567 rescueState
.sensor
.distanceToHomeM
= rescueState
.sensor
.distanceToHomeCm
/ 100.0f
;
568 rescueState
.sensor
.groundSpeedCmS
= gpsSol
.groundSpeed
; // cm/s
570 rescueState
.sensor
.gpsDataIntervalSeconds
= getGpsDataIntervalSeconds();
571 // Range from 10ms (100hz) to 1000ms (1Hz). Intended to cover common GPS data rates and exclude unusual values.
573 rescueState
.sensor
.velocityToHomeCmS
= ((prevDistanceToHomeCm
- rescueState
.sensor
.distanceToHomeCm
) / rescueState
.sensor
.gpsDataIntervalSeconds
);
574 // positive = towards home. First value is useless since prevDistanceToHomeCm was zero.
575 prevDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
577 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 2, lrintf(rescueState
.sensor
.velocityToHomeCmS
));
578 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 0, lrintf(rescueState
.sensor
.velocityToHomeCmS
));
581 // This function flashes "RESCUE N/A" in the OSD if:
582 // 1. sensor healthy - GPS data is being received.
583 // 2. GPS has a 3D fix.
584 // 3. GPS number of satellites is greater than or equal to the minimum configured satellite count.
585 // Note 1: cannot arm without the required number of sats
586 // hence this flashing indicates that after having enough sats, we now have below the minimum and the rescue likely would fail
587 // Note 2: this function does not take into account the distance from home
588 // The sanity checks are independent, this just provides the OSD warning
589 static bool checkGPSRescueIsAvailable(void)
591 static timeUs_t previousTimeUs
= 0; // Last time LowSat was checked
592 const timeUs_t currentTimeUs
= micros();
593 static int8_t secondsLowSats
= 0; // Minimum sat detection
594 static bool lowsats
= false;
595 static bool noGPSfix
= false;
598 if (!gpsIsHealthy() || !STATE(GPS_FIX_HOME
)) {
602 // Things that should run at a low refresh rate >> ~1hz
603 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
604 if (dTime
< 1000000) { //1hz
605 if (noGPSfix
|| lowsats
) {
611 previousTimeUs
= currentTimeUs
;
613 if (!STATE(GPS_FIX
)) {
620 secondsLowSats
= constrain(secondsLowSats
+ ((gpsSol
.numSat
< GPS_MIN_SAT_COUNT
) ? 1 : -1), 0, 2);
621 if (secondsLowSats
== 2) {
631 void disarmOnImpact(void)
633 if (rescueState
.sensor
.accMagnitude
> rescueState
.intent
.disarmThreshold
) {
634 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
635 disarm(DISARM_REASON_GPS_RESCUE
);
643 const float distanceToLandingAreaM
= rescueState
.sensor
.distanceToHomeM
- (rescueState
.intent
.targetLandingAltitudeCm
/ 200.0f
);
644 // considers home to be a circle half landing height around home to avoid overshooting home point
645 rescueState
.intent
.proximityToLandingArea
= constrainf(distanceToLandingAreaM
/ rescueState
.intent
.descentDistanceM
, 0.0f
, 1.0f
);
646 rescueState
.intent
.velocityPidCutoffModifier
= 2.5f
- rescueState
.intent
.proximityToLandingArea
;
647 // 1.5 when starting descent, 2.5 when almost landed; multiplier for velocity step cutoff filter
648 rescueState
.intent
.targetVelocityCmS
= gpsRescueConfig()->rescueGroundspeed
* rescueState
.intent
.proximityToLandingArea
;
649 // reduce target velocity as we get closer to home. Zero within 2m of home, reducing risk of overshooting.
650 // if quad drifts further than 2m away from home, should by then have rotated towards home, so pitch is allowed
651 rescueState
.intent
.rollAngleLimitDeg
= gpsRescueConfig()->maxRescueAngle
* rescueState
.intent
.proximityToLandingArea
;
652 // reduce roll capability when closer to home, none within final 2m
655 // configure altitude step for descent, considering interval between altitude readings
656 rescueState
.intent
.altitudeStep
= -1.0f
* rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
658 // descend more slowly if return altitude is less than 20m
659 const float descentAttenuator
= rescueState
.intent
.returnAltitudeCm
/ 2000.0f
;
660 if (descentAttenuator
< 1.0f
) {
661 rescueState
.intent
.altitudeStep
*= descentAttenuator
;
663 // descend more quickly from higher altitude
664 rescueState
.intent
.descentRateModifier
= constrainf(rescueState
.intent
.targetAltitudeCm
/ 5000.0f
, 0.0f
, 1.0f
);
665 rescueState
.intent
.targetAltitudeCm
+= rescueState
.intent
.altitudeStep
* (1.0f
+ (2.0f
* rescueState
.intent
.descentRateModifier
));
666 // increase descent rate to max of 3x default above 50m, 2x above 25m, 1.2 at 5m, default by ground level.
669 void gpsRescueUpdate(void)
670 // runs at gpsRescueTaskIntervalSeconds, and runs whether or not rescue is active
672 if (!FLIGHT_MODE(GPS_RESCUE_MODE
)) {
673 rescueStop(); // sets phase to RESCUE_IDLE; does nothing else. RESCUE_IDLE tasks still run.
674 } else if (FLIGHT_MODE(GPS_RESCUE_MODE
) && rescueState
.phase
== RESCUE_IDLE
) {
675 rescueStart(); // sets phase to rescue_initialise if we enter GPS Rescue mode while idle
676 rescueAttainPosition(); // Initialise basic parameters when a Rescue starts (can't initialise sensor data reliably)
677 performSanityChecks(); // Initialises sanity check values when a Rescue starts
680 // Will now be in RESCUE_INITIALIZE mode, if just entered Rescue while IDLE, otherwise stays IDLE
682 sensorUpdate(); // always get latest GPS and Altitude data, update ascend and descend rates
684 static bool initialAltitudeLow
= true;
685 static bool initialVelocityLow
= true;
686 rescueState
.isAvailable
= checkGPSRescueIsAvailable();
688 switch (rescueState
.phase
) {
690 // in Idle phase = NOT in GPS Rescue
691 // update the return altitude and descent distance values, to have valid settings immediately they are needed
694 // sanity checks are bypassed in IDLE mode; instead, failure state is always initialised to HEALTHY
695 // target altitude is always set to current altitude.
697 case RESCUE_INITIALIZE
:
698 // Things that should be done at the start of a Rescue
699 rescueState
.intent
.targetLandingAltitudeCm
= 100.0f
* gpsRescueConfig()->targetLandingAltitudeM
;
700 if (!STATE(GPS_FIX_HOME
)) {
701 // we didn't get a home point on arming
702 rescueState
.failure
= RESCUE_NO_HOME_POINT
;
703 // will result in a disarm via the sanity check system, with delay if switch induced
704 // alternative is to prevent the rescue by returning to IDLE, but this could cause flyaways
705 } else if (rescueState
.sensor
.distanceToHomeM
< gpsRescueConfig()->minRescueDth
) {
706 if (rescueState
.sensor
.distanceToHomeM
< 5.0f
&& rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.targetLandingAltitudeCm
) {
707 // attempted initiation within 5m of home, and 'on the ground' -> instant disarm, for safety reasons
708 rescueState
.phase
= RESCUE_ABORT
;
710 // Otherwise, attempted initiation inside minimum activation distance, at any height -> landing mode
711 rescueState
.intent
.altitudeStep
= -rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
712 rescueState
.intent
.targetVelocityCmS
= 0; // zero forward velocity
713 rescueState
.intent
.pitchAngleLimitDeg
= 0; // flat on pitch
714 rescueState
.intent
.rollAngleLimitDeg
= 0.0f
; // flat on roll also
715 rescueState
.intent
.proximityToLandingArea
= 0.0f
; // force velocity iTerm to zero
716 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueState
.intent
.altitudeStep
;
717 rescueState
.phase
= RESCUE_LANDING
;
718 // start landing from current altitude
721 rescueState
.phase
= RESCUE_ATTAIN_ALT
;
722 rescueState
.intent
.secondsFailing
= 0; // reset the sanity check timer for the climb
723 initialAltitudeLow
= (rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.returnAltitudeCm
);
724 rescueState
.intent
.yawAttenuator
= 0.0f
;
725 rescueState
.intent
.targetVelocityCmS
= rescueState
.sensor
.velocityToHomeCmS
;
726 rescueState
.intent
.pitchAngleLimitDeg
= 0.0f
; // no pitch
727 rescueState
.intent
.rollAngleLimitDeg
= 0.0f
; // no roll until flying home
728 rescueState
.intent
.altitudeStep
= 0.0f
;
729 rescueState
.intent
.descentRateModifier
= 0.0f
;
730 rescueState
.intent
.velocityPidCutoffModifier
= 1.0f
; // normal cutoff until descending when increases 150->250% during descent
731 rescueState
.intent
.proximityToLandingArea
= 0.0f
; // force velocity iTerm to zero
732 rescueState
.intent
.velocityItermRelax
= 0.0f
; // and don't accumulate any
736 case RESCUE_ATTAIN_ALT
:
737 // gradually increment the target altitude until the craft reaches target altitude
738 // note that this can mean the target altitude may increase above returnAltitude if the craft lags target
739 // sanity check will abort if altitude gain is blocked for a cumulative period
740 rescueState
.intent
.altitudeStep
= ((initialAltitudeLow
) ? gpsRescueConfig()->ascendRate
: -1.0f
* gpsRescueConfig()->descendRate
) * rescueState
.sensor
.gpsRescueTaskIntervalSeconds
;
741 const bool currentAltitudeLow
= rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.returnAltitudeCm
;
742 if (initialAltitudeLow
== currentAltitudeLow
) {
743 // we started low, and still are low; also true if we started high, and still are too high
744 rescueState
.intent
.targetAltitudeCm
+= rescueState
.intent
.altitudeStep
;
746 // target altitude achieved - move on to ROTATE phase, returning at target altitude
747 rescueState
.intent
.targetAltitudeCm
= rescueState
.intent
.returnAltitudeCm
;
748 rescueState
.intent
.altitudeStep
= 0.0f
;
749 rescueState
.phase
= RESCUE_ROTATE
;
752 rescueState
.intent
.targetVelocityCmS
= rescueState
.sensor
.velocityToHomeCmS
;
753 // gives velocity P and I no error that otherwise would be present due to velocity drift at the start of the rescue
757 if (rescueState
.intent
.yawAttenuator
< 1.0f
) { // acquire yaw authority over one second
758 rescueState
.intent
.yawAttenuator
+= rescueState
.sensor
.gpsRescueTaskIntervalSeconds
;
760 if (rescueState
.sensor
.absErrorAngle
< 30.0f
) {
761 rescueState
.intent
.pitchAngleLimitDeg
= gpsRescueConfig()->maxRescueAngle
; // allow pitch
762 rescueState
.phase
= RESCUE_FLY_HOME
; // enter fly home phase
763 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for flight home
764 rescueState
.intent
.proximityToLandingArea
= 1.0f
; // velocity iTerm activated, initialise proximity for descent phase at 1.0
766 initialVelocityLow
= rescueState
.sensor
.velocityToHomeCmS
< gpsRescueConfig()->rescueGroundspeed
; // used to set direction of velocity target change
767 rescueState
.intent
.targetVelocityCmS
= rescueState
.sensor
.velocityToHomeCmS
;
770 case RESCUE_FLY_HOME
:
771 if (rescueState
.intent
.yawAttenuator
< 1.0f
) { // be sure to accumulate full yaw authority
772 rescueState
.intent
.yawAttenuator
+= rescueState
.sensor
.gpsRescueTaskIntervalSeconds
;
775 // velocity PIDs are now active
776 // update target velocity gradually, aiming for rescueGroundspeed with a time constant of 1.0s
777 const float targetVelocityError
= gpsRescueConfig()->rescueGroundspeed
- rescueState
.intent
.targetVelocityCmS
;
778 const float velocityTargetStep
= rescueState
.sensor
.gpsRescueTaskIntervalSeconds
* targetVelocityError
;
779 // velocityTargetStep is positive when starting low, negative when starting high
780 const bool targetVelocityIsLow
= rescueState
.intent
.targetVelocityCmS
< gpsRescueConfig()->rescueGroundspeed
;
781 if (initialVelocityLow
== targetVelocityIsLow
) {
782 // also true if started faster than target velocity and target is still high
783 rescueState
.intent
.targetVelocityCmS
+= velocityTargetStep
;
786 rescueState
.intent
.velocityItermRelax
+= 0.5f
* rescueState
.sensor
.gpsRescueTaskIntervalSeconds
* (1.0f
- rescueState
.intent
.velocityItermRelax
);
787 // slowly introduce velocity iTerm accumulation at start, goes 0 ->1 with time constant 2.0s
788 // there is always a lot of lag at the start
790 rescueState
.intent
.velocityPidCutoffModifier
= 2.0f
- rescueState
.intent
.velocityItermRelax
;
791 // higher velocity cutoff for initial few seconds to improve accuracy; can be smoother later
793 rescueState
.intent
.rollAngleLimitDeg
= 0.5f
* rescueState
.intent
.velocityItermRelax
* gpsRescueConfig()->maxRescueAngle
;
794 // gradually gain roll capability to max of half of max pitch angle
797 if (rescueState
.sensor
.distanceToHomeM
<= rescueState
.intent
.descentDistanceM
) {
798 rescueState
.phase
= RESCUE_DESCENT
;
799 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for descent
805 // attenuate velocity and altitude targets while updating the heading to home
806 if (rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.targetLandingAltitudeCm
) {
807 // enter landing mode once below landing altitude
808 rescueState
.phase
= RESCUE_LANDING
;
809 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for landing
815 // Reduce altitude target steadily until impact, then disarm.
816 // control yaw angle and throttle and pitch, attenuate velocity, roll and pitch iTerm
817 // increase velocity smoothing cutoff as we get closer to ground
822 case RESCUE_COMPLETE
:
827 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
828 disarm(DISARM_REASON_FAILSAFE
);
829 rescueState
.intent
.secondsFailing
= 0; // reset sanity timers so we can re-arm
833 case RESCUE_DO_NOTHING
:
841 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 3, lrintf(rescueState
.intent
.targetAltitudeCm
));
842 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 3, lrintf(rescueState
.intent
.targetAltitudeCm
));
843 DEBUG_SET(DEBUG_RTH
, 0, lrintf(rescueState
.intent
.maxAltitudeCm
));
845 performSanityChecks();
846 rescueAttainPosition();
851 float gpsRescueGetYawRate(void)
856 float gpsRescueGetThrottle(void)
858 // Calculated a desired commanded throttle scaled from 0.0 to 1.0 for use in the mixer.
859 // We need to compensate for min_check since the throttle value set by gps rescue
860 // is based on the raw rcCommand value commanded by the pilot.
861 float commandedThrottle
= scaleRangef(rescueThrottle
, MAX(rxConfig()->mincheck
, PWM_RANGE_MIN
), PWM_RANGE_MAX
, 0.0f
, 1.0f
);
862 commandedThrottle
= constrainf(commandedThrottle
, 0.0f
, 1.0f
);
864 return commandedThrottle
;
867 bool gpsRescueIsConfigured(void)
869 return failsafeConfig()->failsafe_procedure
== FAILSAFE_PROCEDURE_GPS_RESCUE
|| isModeActivationConditionPresent(BOXGPSRESCUE
);
872 bool gpsRescueIsAvailable(void)
874 return rescueState
.isAvailable
;
877 bool gpsRescueIsDisabled(void)
878 // used for OSD warning
880 return (!STATE(GPS_FIX_HOME
));
884 bool gpsRescueDisableMag(void)
886 return ((!gpsRescueConfig()->useMag
|| magForceDisable
) && (rescueState
.phase
>= RESCUE_INITIALIZE
) && (rescueState
.phase
<= RESCUE_LANDING
));