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/maths.h"
30 #include "common/utils.h"
32 #include "drivers/time.h"
36 #include "config/config.h"
38 #include "fc/rc_controls.h"
39 #include "fc/rc_modes.h"
40 #include "fc/runtime_config.h"
42 #include "flight/failsafe.h"
43 #include "flight/imu.h"
44 #include "flight/pid.h"
45 #include "flight/position.h"
48 #include "pg/pg_ids.h"
52 #include "sensors/acceleration.h"
54 #include "gps_rescue.h"
57 RESCUE_SANITY_OFF
= 0,
80 RESCUE_CRASH_FLIP_DETECTED
,
84 } rescueFailureState_e
;
88 float returnAltitudeCm
;
89 float targetAltitudeCm
;
90 float targetLandingAltitudeCm
;
91 float targetVelocityCmS
;
92 float pitchAngleLimitDeg
;
93 float rollAngleLimitDeg
;
94 float descentDistanceM
;
95 int8_t secondsFailing
;
97 float descentRateModifier
;
99 float disarmThreshold
;
103 float currentAltitudeCm
;
104 float distanceToHomeCm
;
105 float distanceToHomeM
;
106 uint16_t groundSpeedCmS
;
107 int16_t directionToHome
;
111 float gpsDataIntervalSeconds
;
112 float altitudeDataIntervalSeconds
;
113 float velocityToHomeCmS
;
114 float alitutudeStepCm
;
118 } rescueSensorData_s
;
122 rescueFailureState_e failure
;
123 rescueSensorData_s sensor
;
124 rescueIntent_s intent
;
134 #define GPS_RESCUE_MAX_YAW_RATE 180 // deg/sec max yaw rate
135 #define GPS_RESCUE_MIN_DESCENT_DIST_M 5 // minimum descent distance
136 #define GPS_RESCUE_MAX_ITERM_VELOCITY 1000 // max iterm value for velocity
137 #define GPS_RESCUE_MAX_ITERM_THROTTLE 200 // max iterm value for throttle
138 #define GPS_RESCUE_MAX_PITCH_RATE 3000 // max change in pitch per second in degrees * 100
139 #define GPS_RESCUE_DISARM_THRESHOLD 2.0f // disarm threshold in G's
142 #define GPS_RESCUE_USE_MAG true
144 #define GPS_RESCUE_USE_MAG false
147 PG_REGISTER_WITH_RESET_TEMPLATE(gpsRescueConfig_t
, gpsRescueConfig
, PG_GPS_RESCUE
, 3);
149 PG_RESET_TEMPLATE(gpsRescueConfig_t
, gpsRescueConfig
,
151 .altitudeMode
= MAX_ALT
,
152 .rescueAltitudeBufferM
= 10,
153 .ascendRate
= 500, // cm/s, for altitude corrections on ascent
155 .initialAltitudeM
= 30,
156 .rescueGroundspeed
= 500,
160 .descentDistanceM
= 20,
161 .descendRate
= 100, // cm/s, minimum for descent and landing phase, or for descending if starting high ascent
162 .targetLandingAltitudeM
= 4,
166 .throttleHover
= 1275,
168 .allowArmingWithoutFix
= false,
169 .sanityChecks
= RESCUE_SANITY_FS_ONLY
,
180 .useMag
= GPS_RESCUE_USE_MAG
183 static float rescueThrottle
;
184 static float rescueYaw
;
185 float gpsRescueAngle
[ANGLE_INDEX_COUNT
] = { 0, 0 };
186 bool magForceDisable
= false;
187 static bool newGPSData
= false;
188 static pt2Filter_t throttleDLpf
;
189 static pt3Filter_t pitchLpf
;
191 rescueState_s rescueState
;
193 void gpsRescueInit(void)
195 const float sampleTimeS
= HZ_TO_INTERVAL(TASK_ALTITUDE_RATE_HZ
);
197 const float throttleDCutoffHz
= positionConfig()->altitude_d_lpf
/ 100.0f
;
198 const float throttleDCutoffGain
= pt2FilterGain(throttleDCutoffHz
, sampleTimeS
);
199 pt2FilterInit(&throttleDLpf
, throttleDCutoffGain
);
201 const float pitchCutoffHz
= 4.0f
;
202 const float pitchCutoffGain
= pt3FilterGain(pitchCutoffHz
, sampleTimeS
);
203 pt3FilterInit(&pitchLpf
, pitchCutoffGain
);
207 If we have new GPS data, update home heading if possible and applicable.
209 void gpsRescueNewGpsData(void)
214 static void rescueStart(void)
216 rescueState
.phase
= RESCUE_INITIALIZE
;
219 static void rescueStop(void)
221 rescueState
.phase
= RESCUE_IDLE
;
224 // Things that need to run when GPS Rescue is enabled, and while armed, but while there is no Rescue in place
225 static void setReturnAltitude(void)
227 // Hold maxAltitude at zero while disarmed, but if set_home_point_once is true, hold maxAlt until power cycled
228 if (!ARMING_FLAG(ARMED
) && !gpsConfig()->gps_set_home_point_once
) {
229 rescueState
.intent
.maxAltitudeCm
= 0.0f
;
233 // While armed, but not during the rescue, update the max altitude value
234 rescueState
.intent
.maxAltitudeCm
= fmaxf(rescueState
.sensor
.currentAltitudeCm
, rescueState
.intent
.maxAltitudeCm
);
237 // set the target altitude to current values, so there will be no D kick on first run
238 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
240 // Keep the descent distance and intended altitude up to date with latest GPS values
241 rescueState
.intent
.descentDistanceM
= constrainf(rescueState
.sensor
.distanceToHomeM
, GPS_RESCUE_MIN_DESCENT_DIST_M
, gpsRescueConfig()->descentDistanceM
);
242 const float initialAltitudeCm
= gpsRescueConfig()->initialAltitudeM
* 100.0f
;
243 const float rescueAltitudeBufferCm
= gpsRescueConfig()->rescueAltitudeBufferM
* 100.0f
;
244 switch (gpsRescueConfig()->altitudeMode
) {
246 rescueState
.intent
.returnAltitudeCm
= initialAltitudeCm
;
249 rescueState
.intent
.returnAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueAltitudeBufferCm
;
253 rescueState
.intent
.returnAltitudeCm
= rescueState
.intent
.maxAltitudeCm
+ rescueAltitudeBufferCm
;
259 static void rescueAttainPosition(void)
261 // runs at 100hz, but only updates RPYT settings when new GPS Data arrives and when not in idle phase.
262 static float previousVelocityError
= 0.0f
;
263 static float velocityI
= 0.0f
;
264 static float previousVelocityD
= 0.0f
; // for smoothing
265 static float previousPitchAdjustment
= 0.0f
;
266 static float throttleI
= 0.0f
;
267 static float previousAltitudeError
= 0.0f
;
268 static int16_t throttleAdjustment
= 0;
270 switch (rescueState
.phase
) {
272 // values to be returned when no rescue is active
273 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
274 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
275 rescueThrottle
= rcCommand
[THROTTLE
];
277 case RESCUE_INITIALIZE
:
278 // Initialize internal variables each time GPS Rescue is started
279 previousVelocityError
= 0.0f
;
281 previousVelocityD
= 0.0f
;
282 previousPitchAdjustment
= 0.0f
;
284 previousAltitudeError
= 0.0f
;
285 throttleAdjustment
= 0;
286 rescueState
.intent
.disarmThreshold
= GPS_RESCUE_DISARM_THRESHOLD
;
288 case RESCUE_DO_NOTHING
:
289 // 20s of slow descent for switch induced sanity failures to allow time to recover
290 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
291 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
292 rescueThrottle
= gpsRescueConfig()->throttleHover
- 100;
299 Altitude (throttle) controller
301 // currentAltitudeCm is updated at TASK_GPS_RESCUE_RATE_HZ
302 const float altitudeError
= (rescueState
.intent
.targetAltitudeCm
- rescueState
.sensor
.currentAltitudeCm
) * 0.01f
;
303 // height above target in metres (negative means too low)
304 // at the start, the target starts at current altitude plus one step. Increases stepwise to intended value.
307 const float throttleP
= gpsRescueConfig()->throttleP
* altitudeError
;
310 throttleI
+= 0.1f
* gpsRescueConfig()->throttleI
* altitudeError
* rescueState
.sensor
.altitudeDataIntervalSeconds
;
311 throttleI
= constrainf(throttleI
, -1.0f
* GPS_RESCUE_MAX_ITERM_THROTTLE
, 1.0f
* GPS_RESCUE_MAX_ITERM_THROTTLE
);
312 // up to 20% increase in throttle from I alone
314 // D component is error based, so includes positive boost when climbing and negative boost on descent
315 float verticalSpeed
= ((altitudeError
- previousAltitudeError
) / rescueState
.sensor
.altitudeDataIntervalSeconds
);
316 previousAltitudeError
= altitudeError
;
317 verticalSpeed
+= rescueState
.intent
.descentRateModifier
* verticalSpeed
;
318 // add up to 2x D when descent rate is faster
320 float throttleD
= pt2FilterApply(&throttleDLpf
, verticalSpeed
);
322 rescueState
.intent
.disarmThreshold
= GPS_RESCUE_DISARM_THRESHOLD
- throttleD
/ 15.0f
; // make disarm more likely if throttle D is high
324 throttleD
= gpsRescueConfig()->throttleD
* throttleD
;
326 // acceleration component not currently implemented - was needed previously due to GPS lag, maybe not needed now.
328 float tiltAdjustment
= 1.0f
- getCosTiltAngle(); // 0 = flat, gets to 0.2 correcting on a windy day
329 tiltAdjustment
*= (gpsRescueConfig()->throttleHover
- 1000);
330 // if hover is 1300, and adjustment .2, this gives us 0.2*300 or 60 of extra throttle, not much, but useful
331 // too much and landings with lots of pitch adjustment, eg windy days, can be a problem
333 throttleAdjustment
= throttleP
+ throttleI
+ throttleD
+ tiltAdjustment
;
335 rescueThrottle
= gpsRescueConfig()->throttleHover
+ throttleAdjustment
;
336 rescueThrottle
= constrainf(rescueThrottle
, gpsRescueConfig()->throttleMin
, gpsRescueConfig()->throttleMax
);
337 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 0, lrintf(throttleP
));
338 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 1, lrintf(throttleD
));
341 Heading / yaw controller
343 // simple yaw P controller with roll mixed in.
344 // attitude.values.yaw is set by imuCalculateEstimatedAttitude() and is updated from GPS while groundspeed exceeds 2 m/s
345 // below 2m/s groundspeed, the IMU uses gyro to estimate yaw attitude change from previous values
346 // above 2m/s, GPS course over ground us ysed to 'correct' the IMU heading
347 // 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
348 // the craft should not return much less than 5m/s during the rescue or the GPS corrections may be inaccurate.
349 // the faster the return speed, the more accurate the IMU will be, but the consequences of IMU error at the start are greater
350 // A compass (magnetometer) is vital for accurate GPS rescue at slow speeds, but must be calibrated and validated.
352 rescueYaw
= rescueState
.sensor
.errorAngle
* gpsRescueConfig()->yawP
* rescueState
.intent
.yawAttenuator
* 0.1f
;
353 rescueYaw
= constrainf(rescueYaw
, -GPS_RESCUE_MAX_YAW_RATE
, GPS_RESCUE_MAX_YAW_RATE
);
354 // rescueYaw is the yaw rate in deg/s to correct the heading error
356 const float rollMixAttenuator
= constrainf(1.0f
- fabsf(rescueYaw
) * 0.01f
, 0.0f
, 1.0f
);
357 // less roll at higher yaw rates, no roll at 100 deg/s of yaw
358 const float rollAdjustment
= -rescueYaw
* gpsRescueConfig()->rollMix
* rollMixAttenuator
;
359 // 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
360 // when gpsRescueConfig()->rollMix is zero, there is no roll adjustment
361 // rollAdjustment is degrees * 100
362 // note that the roll element has the opposite sign to the yaw element *before* GET_DIRECTION
364 const float rollLimit
= 100.0f
* rescueState
.intent
.rollAngleLimitDeg
;
365 gpsRescueAngle
[AI_ROLL
] = constrainf(rollAdjustment
, -rollLimit
, rollLimit
);
366 // gpsRescueAngle is added to the normal roll Angle Mode corrections in pid.c
368 rescueYaw
*= GET_DIRECTION(rcControlsConfig()->yaw_control_reversed
);
371 Pitch / velocity controller
373 static float pitchAdjustment
= 0.0f
;
376 const float sampleIntervalNormaliseFactor
= rescueState
.sensor
.gpsDataIntervalSeconds
* 10.0f
;
378 const float velocityError
= (rescueState
.intent
.targetVelocityCmS
- rescueState
.sensor
.velocityToHomeCmS
);
379 // velocityError is in cm per second, positive means too slow.
380 // NB positive pitch setpoint means nose down.
383 const float velocityP
= velocityError
* gpsRescueConfig()->velP
;
386 velocityI
+= 0.01f
* gpsRescueConfig()->velI
* velocityError
* sampleIntervalNormaliseFactor
;
387 // increase amount added when GPS sample rate is slower
388 velocityI
= constrainf(velocityI
, -1.0f
* GPS_RESCUE_MAX_ITERM_VELOCITY
, 1.0f
* GPS_RESCUE_MAX_ITERM_VELOCITY
);
389 // I component alone cannot exceed a pitch angle of 10%
392 float velocityD
= ((velocityError
- previousVelocityError
) / sampleIntervalNormaliseFactor
);
393 previousVelocityError
= velocityError
;
394 // simple first order filter on derivative with k = 0.5 for 200ms steps
395 velocityD
= previousVelocityD
+ rescueState
.sensor
.filterK
* (velocityD
- previousVelocityD
);
396 previousVelocityD
= velocityD
;
397 velocityD
*= gpsRescueConfig()->velD
;
399 const float velocityIAttenuator
= rescueState
.intent
.targetVelocityCmS
/ gpsRescueConfig()->rescueGroundspeed
;
400 // reduces iTerm as target velocity decreases, to minimise overshoot during deceleration to landing phase
402 pitchAdjustment
= velocityP
+ velocityD
;
403 if (rescueState
.phase
== RESCUE_FLY_HOME
) {
404 pitchAdjustment
*= 0.7f
; // attenuate pitch PIDs during main fly home phase, tighten up in descent.
406 pitchAdjustment
+= velocityI
* velocityIAttenuator
;
408 const float movingAvgPitchAdjustment
= 0.5f
* (previousPitchAdjustment
+ pitchAdjustment
);
409 // moving average seems to work best here, a lot of sequential up and down in velocity data
410 previousPitchAdjustment
= pitchAdjustment
;
411 pitchAdjustment
= movingAvgPitchAdjustment
;
412 // pitchAdjustment is the absolute Pitch angle adjustment value in degrees * 100
413 // it gets added to the normal level mode Pitch adjustments in pid.c
414 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 0, lrintf(velocityP
));
415 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 1, lrintf(velocityD
));
418 const float pitchAdjustmentFiltered
= pt3FilterApply(&pitchLpf
, pitchAdjustment
);
419 // upsampling and smoothing of pitch angle steps
421 const float pitchAngleLimit
= rescueState
.intent
.pitchAngleLimitDeg
* 100.0f
;
422 gpsRescueAngle
[AI_PITCH
] = constrainf(pitchAdjustmentFiltered
, -pitchAngleLimit
, pitchAngleLimit
);
423 // this angle gets added to the normal pitch Angle Mode control values in pid.c - will be seen in pitch setpoint
425 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 3, lrintf(rescueState
.intent
.targetVelocityCmS
));
426 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 1, lrintf(rescueState
.intent
.targetVelocityCmS
));
429 static void performSanityChecks(void)
431 static timeUs_t previousTimeUs
= 0; // Last time Stalled/LowSat was checked
432 static float prevAltitudeCm
= 0.0f
; // to calculate ascent or descent change
433 static float prevTargetAltitudeCm
= 0.0f
; // to calculate ascent or descent target change
434 static float previousDistanceToHomeCm
= 0.0f
; // to check that we are returning
435 static int8_t secondsLowSats
= 0; // Minimum sat detection
436 static int8_t secondsDoingNothing
= 0; // Limit on doing nothing
437 const timeUs_t currentTimeUs
= micros();
439 if (rescueState
.phase
== RESCUE_IDLE
) {
440 rescueState
.failure
= RESCUE_HEALTHY
;
442 } else if (rescueState
.phase
== RESCUE_INITIALIZE
) {
443 // Initialize these variables each time a GPS Rescue is started
444 previousTimeUs
= currentTimeUs
;
445 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
446 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
447 previousDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
449 secondsDoingNothing
= 0;
453 // Handle events that set a failure mode to other than healthy.
454 // Disarm via Abort when sanity on, or for hard Rx loss in FS_ONLY mode
455 // Otherwise allow 20s of semi-controlled descent with impact disarm detection
456 const bool hardFailsafe
= !rxIsReceivingSignal();
457 if (rescueState
.failure
!= RESCUE_HEALTHY
) {
458 if (gpsRescueConfig()->sanityChecks
== RESCUE_SANITY_ON
) {
459 rescueState
.phase
= RESCUE_ABORT
;
460 } else if ((gpsRescueConfig()->sanityChecks
== RESCUE_SANITY_FS_ONLY
) && hardFailsafe
) {
461 rescueState
.phase
= RESCUE_ABORT
;
463 // even with sanity checks off,
464 rescueState
.phase
= RESCUE_DO_NOTHING
; // 20s semi-controlled descent with impact detection, then abort
468 // Crash detection is enabled in all rescues. If triggered, immediately disarm.
469 if (crashRecoveryModeActive()) {
470 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
471 disarm(DISARM_REASON_CRASH_PROTECTION
);
475 // Check if GPS comms are healthy
476 // ToDo - check if we have an altitude reading; if we have Baro, we can use Landing mode for controlled descent without GPS
477 if (!rescueState
.sensor
.healthy
) {
478 rescueState
.failure
= RESCUE_GPSLOST
;
481 // Things that should run at a low refresh rate (such as flyaway detection, etc) will be checked at 1Hz
482 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
483 if (dTime
< 1000000) { //1hz
486 previousTimeUs
= currentTimeUs
;
488 // checks that we are getting closer to home.
489 // if the quad is stuck, or if GPS data packets stop, there will be no change in distance to home
490 // we can't use rescueState.sensor.currentVelocity because it will be held at the last good value if GPS data updates stop
491 if (rescueState
.phase
== RESCUE_FLY_HOME
) {
492 const float velocityToHomeCmS
= previousDistanceToHomeCm
- rescueState
.sensor
.distanceToHomeCm
; // cm/s
493 previousDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
494 rescueState
.intent
.secondsFailing
+= (velocityToHomeCmS
< 0.5f
* rescueState
.intent
.targetVelocityCmS
) ? 1 : -1;
495 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 15);
496 if (rescueState
.intent
.secondsFailing
== 15) {
498 //If there is a mag and has not been disabled, we have to assume is healthy and has been used in imu.c
499 if (sensors(SENSOR_MAG
) && gpsRescueConfig()->useMag
&& !magForceDisable
) {
500 //Try again with mag disabled
501 magForceDisable
= true;
502 rescueState
.intent
.secondsFailing
= 0;
506 rescueState
.failure
= RESCUE_FLYAWAY
;
511 secondsLowSats
+= (!STATE(GPS_FIX
) || (gpsSol
.numSat
< GPS_MIN_SAT_COUNT
)) ? 1 : -1;
512 secondsLowSats
= constrain(secondsLowSats
, 0, 10);
514 if (secondsLowSats
== 10) {
515 rescueState
.failure
= RESCUE_LOWSATS
;
519 // These conditions ignore sanity mode settings, and apply in all rescues, to handle getting stuck in a climb or descend
521 const float actualAltitudeChange
= rescueState
.sensor
.currentAltitudeCm
- prevAltitudeCm
;
522 const float targetAltitudeChange
= rescueState
.intent
.targetAltitudeCm
- prevTargetAltitudeCm
;
523 const float ratio
= actualAltitudeChange
/ targetAltitudeChange
;
524 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
525 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
527 if (rescueState
.phase
== RESCUE_LANDING
) {
528 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
529 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
530 if (rescueState
.intent
.secondsFailing
== 10) {
531 rescueState
.phase
= RESCUE_ABORT
;
532 // Landing mode shouldn't take more than 10s
534 } else if (rescueState
.phase
== RESCUE_ATTAIN_ALT
|| rescueState
.phase
== RESCUE_DESCENT
) {
535 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
536 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
537 if (rescueState
.intent
.secondsFailing
== 10) {
538 rescueState
.phase
= RESCUE_LANDING
;
539 rescueState
.intent
.secondsFailing
= 0;
540 // if can't climb, or slow descending, enable impact detection and time out in 10s
542 } else if (rescueState
.phase
== RESCUE_DO_NOTHING
) {
543 secondsDoingNothing
= MIN(secondsDoingNothing
+ 1, 20);
544 if (secondsDoingNothing
== 20) {
545 rescueState
.phase
= RESCUE_ABORT
;
546 // time-limited semi-controlled fall with impact detection
550 DEBUG_SET(DEBUG_RTH
, 2, (rescueState
.failure
* 10 + rescueState
.phase
));
551 DEBUG_SET(DEBUG_RTH
, 3, (rescueState
.intent
.secondsFailing
* 100 + secondsLowSats
));
554 static void sensorUpdate(void)
556 static float prevDistanceToHomeCm
= 0.0f
;
557 const timeUs_t currentTimeUs
= micros();
559 static timeUs_t previousAltitudeDataTimeUs
= 0;
560 const timeDelta_t altitudeDataIntervalUs
= cmpTimeUs(currentTimeUs
, previousAltitudeDataTimeUs
);
561 rescueState
.sensor
.altitudeDataIntervalSeconds
= altitudeDataIntervalUs
* 0.000001f
;
562 previousAltitudeDataTimeUs
= currentTimeUs
;
564 rescueState
.sensor
.currentAltitudeCm
= getAltitude();
566 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 2, lrintf(rescueState
.sensor
.currentAltitudeCm
));
567 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 2, lrintf(rescueState
.sensor
.currentAltitudeCm
));
568 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 0, rescueState
.sensor
.groundSpeedCmS
); // groundspeed cm/s
569 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 1, gpsSol
.groundCourse
); // degrees * 10
570 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 2, attitude
.values
.yaw
); // degrees * 10
571 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 3, rescueState
.sensor
.directionToHome
); // degrees * 10
573 rescueState
.sensor
.healthy
= gpsIsHealthy();
575 if (rescueState
.phase
== RESCUE_LANDING
) {
576 // do this at sensor update rate, not the much slower GPS rate, for quick disarm
577 rescueState
.sensor
.accMagnitude
= (float) sqrtf(sq(acc
.accADC
[Z
]) + sq(acc
.accADC
[X
]) + sq(acc
.accADC
[Y
])) * acc
.dev
.acc_1G_rec
;
580 rescueState
.sensor
.directionToHome
= GPS_directionToHome
;
581 rescueState
.sensor
.errorAngle
= (attitude
.values
.yaw
- rescueState
.sensor
.directionToHome
) * 0.1f
;
582 // both attitude and direction are in degrees * 10, errorAngle is degrees
583 if (rescueState
.sensor
.errorAngle
<= -180) {
584 rescueState
.sensor
.errorAngle
+= 360;
585 } else if (rescueState
.sensor
.errorAngle
> 180) {
586 rescueState
.sensor
.errorAngle
-= 360;
588 rescueState
.sensor
.absErrorAngle
= fabsf(rescueState
.sensor
.errorAngle
);
592 // GPS ground speed, velocity and distance to home will be held at last good values if no new packets
595 rescueState
.sensor
.distanceToHomeCm
= GPS_distanceToHomeCm
;
596 rescueState
.sensor
.distanceToHomeM
= rescueState
.sensor
.distanceToHomeCm
/ 100.0f
;
597 rescueState
.sensor
.groundSpeedCmS
= gpsSol
.groundSpeed
; // cm/s
599 static timeUs_t previousGPSDataTimeUs
= 0;
600 const timeDelta_t gpsDataIntervalUs
= cmpTimeUs(currentTimeUs
, previousGPSDataTimeUs
);
601 rescueState
.sensor
.gpsDataIntervalSeconds
= constrainf(gpsDataIntervalUs
* 0.000001f
, 0.01f
, 1.0f
);
602 // Range from 10ms (100hz) to 1000ms (1Hz). Intended to cover common GPS data rates and exclude unusual values.
603 previousGPSDataTimeUs
= currentTimeUs
;
605 rescueState
.sensor
.filterK
= pt1FilterGain(0.8, rescueState
.sensor
.gpsDataIntervalSeconds
);
606 // 0.8341 for 1hz, 0.5013 for 5hz, 0.3345 for 10hz, 0.1674 for 25Hz, etc
608 rescueState
.sensor
.velocityToHomeCmS
= (prevDistanceToHomeCm
- rescueState
.sensor
.distanceToHomeCm
) / rescueState
.sensor
.gpsDataIntervalSeconds
;
609 // positive = towards home. First value is useless since prevDistanceToHomeCm was zero.
610 prevDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
612 rescueState
.sensor
.maxPitchStep
= rescueState
.sensor
.gpsDataIntervalSeconds
* GPS_RESCUE_MAX_PITCH_RATE
;
614 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 2, lrintf(rescueState
.sensor
.velocityToHomeCmS
));
615 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 0, lrintf(rescueState
.sensor
.velocityToHomeCmS
));
619 // This function flashes "RESCUE N/A" in the OSD if:
620 // 1. sensor healthy - GPS data is being received.
621 // 2. GPS has a 3D fix.
622 // 3. GPS number of satellites is greater than or equal to the minimum configured satellite count.
623 // Note 1: cannot arm without the required number of sats
624 // hence this flashing indicates that after having enough sats, we now have below the minimum and the rescue likely would fail
625 // Note 2: this function does not take into account the distance from home
626 // The sanity checks are independent, this just provides the OSD warning
627 static bool checkGPSRescueIsAvailable(void)
629 static timeUs_t previousTimeUs
= 0; // Last time LowSat was checked
630 const timeUs_t currentTimeUs
= micros();
631 static int8_t secondsLowSats
= 0; // Minimum sat detection
632 static bool lowsats
= false;
633 static bool noGPSfix
= false;
636 if (!gpsIsHealthy() || !STATE(GPS_FIX_HOME
)) {
640 // Things that should run at a low refresh rate >> ~1hz
641 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
642 if (dTime
< 1000000) { //1hz
643 if (noGPSfix
|| lowsats
) {
649 previousTimeUs
= currentTimeUs
;
651 if (!STATE(GPS_FIX
)) {
658 secondsLowSats
= constrain(secondsLowSats
+ ((gpsSol
.numSat
< GPS_MIN_SAT_COUNT
) ? 1 : -1), 0, 2);
659 if (secondsLowSats
== 2) {
669 void disarmOnImpact(void)
671 if (rescueState
.sensor
.accMagnitude
> rescueState
.intent
.disarmThreshold
) {
672 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
673 disarm(DISARM_REASON_GPS_RESCUE
);
681 const float distanceToLandingAreaM
= rescueState
.sensor
.distanceToHomeM
- (rescueState
.intent
.targetLandingAltitudeCm
/ 200.0f
);
682 // considers home to be a circle half landing height around home to avoid overshooting home point
683 const float proximityToLandingArea
= constrainf(distanceToLandingAreaM
/ rescueState
.intent
.descentDistanceM
, 0.0f
, 1.0f
);
684 rescueState
.intent
.targetVelocityCmS
= gpsRescueConfig()->rescueGroundspeed
* proximityToLandingArea
;
685 // reduce target velocity as we get closer to home. Zero within 2m of home, reducing risk of overshooting.
686 // if quad drifts further than 2m away from home, should by then have rotated towards home, so pitch is allowed
687 rescueState
.intent
.rollAngleLimitDeg
= gpsRescueConfig()->angle
* proximityToLandingArea
;
688 // reduce roll capability when closer to home, none within final 2m
691 // adjust altitude step for interval between altitude readings
692 rescueState
.intent
.altitudeStep
= -1.0f
* rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
694 // descend more slowly if return altitude is less than 20m
695 const float descentAttenuator
= rescueState
.intent
.returnAltitudeCm
/ 2000.0f
;
696 if (descentAttenuator
< 1.0f
) {
697 rescueState
.intent
.altitudeStep
*= descentAttenuator
;
699 // descend more quickly from higher altitude
700 rescueState
.intent
.descentRateModifier
= constrainf(rescueState
.intent
.targetAltitudeCm
/ 5000.0f
, 0.0f
, 1.0f
);
701 rescueState
.intent
.targetAltitudeCm
+= rescueState
.intent
.altitudeStep
* (1.0f
+ (2.0f
* rescueState
.intent
.descentRateModifier
));
702 // increase descent rate to max of 3x default above 50m, 2x above 25m, 1.2 at 5m, default by ground level.
705 void altitudeAchieved(void)
707 rescueState
.intent
.targetAltitudeCm
= rescueState
.intent
.returnAltitudeCm
;
708 rescueState
.intent
.altitudeStep
= 0;
709 rescueState
.phase
= RESCUE_ROTATE
;
712 void gpsRescueUpdate(void)
713 // this runs a lot faster than the GPS Data update rate, and runs whether or not rescue is active
715 if (!FLIGHT_MODE(GPS_RESCUE_MODE
)) {
716 rescueStop(); // sets phase to RESCUE_IDLE; does nothing else. RESCUE_IDLE tasks still run.
717 } else if (FLIGHT_MODE(GPS_RESCUE_MODE
) && rescueState
.phase
== RESCUE_IDLE
) {
718 rescueStart(); // sets phase to rescue_initialise if we enter GPS Rescue mode while idle
719 rescueAttainPosition(); // Initialise basic parameters when a Rescue starts (can't initialise sensor data reliably)
720 performSanityChecks(); // Initialises sanity check values when a Rescue starts
723 // Will now be in RESCUE_INITIALIZE mode, if just entered Rescue while IDLE, otherwise stays IDLE
725 sensorUpdate(); // always get latest GPS and Altitude data, update ascend and descend rates
727 bool startedLow
= true;
728 rescueState
.isAvailable
= checkGPSRescueIsAvailable();
730 switch (rescueState
.phase
) {
732 // in Idle phase = NOT in GPS Rescue
733 // update the return altitude and descent distance values, to have valid settings immediately they are needed
736 // sanity checks are bypassed in IDLE mode; instead, failure state is always initialised to HEALTHY
737 // target altitude is always set to current altitude.
739 case RESCUE_INITIALIZE
:
740 // Things that should abort the start of a Rescue
741 if (!STATE(GPS_FIX_HOME
)) {
742 // we didn't get a home point on arming
743 rescueState
.failure
= RESCUE_NO_HOME_POINT
;
744 // will result in a disarm via the sanity check system, with delay if switch induced
745 // alternative is to prevent the rescue by returning to IDLE, but this could cause flyaways
746 } else if (rescueState
.sensor
.distanceToHomeM
< gpsRescueConfig()->minRescueDth
) {
747 // Attempt to initiate inside minimum activation distance -> landing mode
748 rescueState
.intent
.altitudeStep
= -rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
749 rescueState
.intent
.targetVelocityCmS
= 0; // zero forward velocity
750 rescueState
.intent
.pitchAngleLimitDeg
= 0; // flat on pitch
751 rescueState
.intent
.rollAngleLimitDeg
= 0.0f
; // flat on roll also
752 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueState
.intent
.altitudeStep
;
753 rescueState
.phase
= RESCUE_LANDING
;
754 // start landing from current altitude
756 rescueState
.phase
= RESCUE_ATTAIN_ALT
;
757 rescueState
.intent
.secondsFailing
= 0; // reset the sanity check timer for the climb
758 rescueState
.intent
.targetLandingAltitudeCm
= 100.0f
* gpsRescueConfig()->targetLandingAltitudeM
;
759 startedLow
= (rescueState
.sensor
.currentAltitudeCm
<= rescueState
.intent
.returnAltitudeCm
);
760 rescueState
.intent
.yawAttenuator
= 0.0f
;
761 rescueState
.intent
.targetVelocityCmS
= 0.0f
; // zero forward velocity while climbing
762 rescueState
.intent
.pitchAngleLimitDeg
= 0.0f
; // no pitch
763 rescueState
.intent
.rollAngleLimitDeg
= 0.0f
; // no roll until flying home
764 rescueState
.intent
.altitudeStep
= 0.0f
;
765 rescueState
.intent
.descentRateModifier
= 0.0f
;
769 case RESCUE_ATTAIN_ALT
:
770 // gradually increment the target altitude until the craft reaches target altitude
771 // note that this can mean the target altitude may increase above returnAltitude if the craft lags target
772 // sanity check will abort if altitude gain is blocked for a cumulative period
774 if (rescueState
.intent
.targetAltitudeCm
< rescueState
.intent
.returnAltitudeCm
) {
775 rescueState
.intent
.altitudeStep
= rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->ascendRate
;
776 } else if (rescueState
.sensor
.currentAltitudeCm
> rescueState
.intent
.returnAltitudeCm
) {
780 if (rescueState
.intent
.targetAltitudeCm
> rescueState
.intent
.returnAltitudeCm
) {
781 rescueState
.intent
.altitudeStep
= -rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
782 } else if (rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.returnAltitudeCm
) {
786 rescueState
.intent
.targetAltitudeCm
+= rescueState
.intent
.altitudeStep
;
790 if (rescueState
.intent
.yawAttenuator
< 1.0f
) { // gradually acquire yaw authority
791 rescueState
.intent
.yawAttenuator
+= 0.01f
;
793 if (rescueState
.sensor
.absErrorAngle
< 30.0f
) {
794 rescueState
.intent
.pitchAngleLimitDeg
= gpsRescueConfig()->angle
; // allow pitch
795 rescueState
.phase
= RESCUE_FLY_HOME
; // enter fly home phase
796 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for flight home
800 case RESCUE_FLY_HOME
:
801 if (rescueState
.intent
.yawAttenuator
< 1.0f
) { // be sure to accumulate full yaw authority
802 rescueState
.intent
.yawAttenuator
+= 0.01f
;
804 // steadily increase target velocity target until full return velocity is acquired
805 if (rescueState
.intent
.targetVelocityCmS
< gpsRescueConfig()->rescueGroundspeed
) {
806 rescueState
.intent
.targetVelocityCmS
+= 0.01f
* gpsRescueConfig()->rescueGroundspeed
;
808 // acquire full roll authority slowly when pointing to home
809 if (rescueState
.sensor
.absErrorAngle
< 10.0f
&& rescueState
.intent
.rollAngleLimitDeg
< gpsRescueConfig()->angle
) {
810 // roll is primarily intended to deal with wind drift causing small yaw errors during return
811 rescueState
.intent
.rollAngleLimitDeg
+= 0.1f
;
815 if (rescueState
.sensor
.distanceToHomeM
<= rescueState
.intent
.descentDistanceM
) {
816 rescueState
.phase
= RESCUE_DESCENT
;
817 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for descent
823 // attenuate velocity and altitude targets while updating the heading to home
824 if (rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.targetLandingAltitudeCm
) {
825 // enter landing mode once below landing altitude
826 rescueState
.phase
= RESCUE_LANDING
;
827 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for landing
833 // Reduce altitude target steadily until impact, then disarm.
834 // control yaw angle and throttle and pitch, attenuate velocity, roll and pitch iTerm
839 case RESCUE_COMPLETE
:
844 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
845 disarm(DISARM_REASON_FAILSAFE
);
849 case RESCUE_DO_NOTHING
:
857 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 3, lrintf(rescueState
.intent
.targetAltitudeCm
));
858 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 3, lrintf(rescueState
.intent
.targetAltitudeCm
));
859 DEBUG_SET(DEBUG_RTH
, 0, lrintf(rescueState
.intent
.maxAltitudeCm
));
861 performSanityChecks();
862 rescueAttainPosition();
867 float gpsRescueGetYawRate(void)
872 float gpsRescueGetThrottle(void)
874 // Calculated a desired commanded throttle scaled from 0.0 to 1.0 for use in the mixer.
875 // We need to compensate for min_check since the throttle value set by gps rescue
876 // is based on the raw rcCommand value commanded by the pilot.
877 float commandedThrottle
= scaleRangef(rescueThrottle
, MAX(rxConfig()->mincheck
, PWM_RANGE_MIN
), PWM_RANGE_MAX
, 0.0f
, 1.0f
);
878 commandedThrottle
= constrainf(commandedThrottle
, 0.0f
, 1.0f
);
880 return commandedThrottle
;
883 bool gpsRescueIsConfigured(void)
885 return failsafeConfig()->failsafe_procedure
== FAILSAFE_PROCEDURE_GPS_RESCUE
|| isModeActivationConditionPresent(BOXGPSRESCUE
);
888 bool gpsRescueIsAvailable(void)
890 return rescueState
.isAvailable
;
893 bool gpsRescueIsDisabled(void)
894 // used for OSD warning
896 return (!STATE(GPS_FIX_HOME
));
900 bool gpsRescueDisableMag(void)
902 return ((!gpsRescueConfig()->useMag
|| magForceDisable
) && (rescueState
.phase
>= RESCUE_INITIALIZE
) && (rescueState
.phase
<= RESCUE_LANDING
));