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/>.
25 #include "build/debug.h"
27 #include "common/axis.h"
28 #include "common/maths.h"
29 #include "common/utils.h"
31 #include "drivers/time.h"
35 #include "config/config.h"
37 #include "fc/rc_controls.h"
38 #include "fc/rc_modes.h"
39 #include "fc/runtime_config.h"
41 #include "flight/failsafe.h"
42 #include "flight/imu.h"
43 #include "flight/pid.h"
44 #include "flight/position.h"
47 #include "pg/pg_ids.h"
51 #include "sensors/acceleration.h"
53 #include "gps_rescue.h"
56 RESCUE_SANITY_OFF
= 0,
79 RESCUE_CRASH_FLIP_DETECTED
,
83 } rescueFailureState_e
;
86 float returnAltitudeCm
;
87 float targetAltitudeCm
;
88 float targetLandingAltitudeCm
;
89 uint16_t targetVelocityCmS
;
90 uint8_t pitchAngleLimitDeg
;
91 int8_t rollAngleLimitDeg
; // must have a negative to positive range
93 float descentDistanceM
;
94 int8_t secondsFailing
;
96 float descentSpeedModifier
;
102 float currentAltitudeCm
;
103 float distanceToHomeCm
;
104 float distanceToHomeM
;
105 uint16_t groundSpeedCmS
; //cm/s
106 int16_t directionToHome
;
110 float gpsDataIntervalSeconds
;
111 float altitudeDataIntervalSeconds
;
112 float velocityToHomeCmS
;
113 float alitutudeStepCm
;
117 } rescueSensorData_s
;
121 rescueFailureState_e failure
;
122 rescueSensorData_s sensor
;
123 rescueIntent_s intent
;
133 #define GPS_RESCUE_MAX_YAW_RATE 180 // deg/sec max yaw rate
134 #define GPS_RESCUE_MIN_DESCENT_DIST_M 5 // minimum descent distance allowed
135 #define GPS_RESCUE_MAX_ITERM_VELOCITY 1000 // max allowed iterm value for velocity
136 #define GPS_RESCUE_MAX_ITERM_THROTTLE 200 // max allowed iterm value for throttle
137 #define GPS_RESCUE_MAX_PITCH_RATE 3000 // max allowed change in pitch per second in degrees * 100
140 #define GPS_RESCUE_USE_MAG true
142 #define GPS_RESCUE_USE_MAG false
145 PG_REGISTER_WITH_RESET_TEMPLATE(gpsRescueConfig_t
, gpsRescueConfig
, PG_GPS_RESCUE
, 3);
147 PG_RESET_TEMPLATE(gpsRescueConfig_t
, gpsRescueConfig
,
149 .altitudeMode
= MAX_ALT
,
150 .rescueAltitudeBufferM
= 10,
151 .ascendRate
= 500, // cm/s, for altitude corrections on ascent
153 .initialAltitudeM
= 30,
154 .rescueGroundspeed
= 500,
158 .descentDistanceM
= 20,
159 .descendRate
= 100, // cm/s, minimum for descent and landing phase, or for descending if starting high ascent
160 .targetLandingAltitudeM
= 5,
164 .throttleHover
= 1275,
166 .allowArmingWithoutFix
= false,
167 .sanityChecks
= RESCUE_SANITY_FS_ONLY
,
177 .useMag
= GPS_RESCUE_USE_MAG
180 static float rescueThrottle
;
181 static float rescueYaw
;
182 float gpsRescueAngle
[ANGLE_INDEX_COUNT
] = { 0, 0 };
183 bool magForceDisable
= false;
184 static bool newGPSData
= false;
185 static pt2Filter_t throttleDLpf
;
186 static pt3Filter_t pitchLpf
;
188 rescueState_s rescueState
;
190 void gpsRescueInit(void)
192 const float sampleTimeS
= HZ_TO_INTERVAL(TASK_ALTITUDE_RATE_HZ
);
194 const float throttleDCutoffHz
= positionConfig()->altitude_d_lpf
/ 100.0f
;
195 const float throttleDCutoffGain
= pt2FilterGain(throttleDCutoffHz
, sampleTimeS
);
196 pt2FilterInit(&throttleDLpf
, throttleDCutoffGain
);
198 const float pitchCutoffHz
= 4.0f
;
199 const float pitchCutoffGain
= pt3FilterGain(pitchCutoffHz
, sampleTimeS
);
200 pt3FilterInit(&pitchLpf
, pitchCutoffGain
);
205 If we have new GPS data, update home heading if possible and applicable.
207 void rescueNewGpsData(void)
212 static void rescueStart()
214 rescueState
.phase
= RESCUE_INITIALIZE
;
217 static void rescueStop()
219 rescueState
.phase
= RESCUE_IDLE
;
222 // Things that need to run when GPS Rescue is enabled, and while armed, but while there is no Rescue in place
223 static void idleTasks()
225 // Don't calculate these values while disarmed
226 if (!ARMING_FLAG(ARMED
)) {
227 rescueState
.sensor
.maxAltitudeCm
= 0;
231 // Don't update any altitude related stuff if we haven't applied a proper altitude offset
232 if (!isAltitudeOffset()) {
236 // Store the max altitude we see not during RTH so we know our fly-back minimum alt
237 rescueState
.sensor
.maxAltitudeCm
= MAX(rescueState
.sensor
.currentAltitudeCm
, rescueState
.sensor
.maxAltitudeCm
);
240 // set the target altitude and velocity to current values, so there will be no D kick on first run
241 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
242 // Keep the descent distance and intended altitude up to date with latest GPS values
243 rescueState
.intent
.descentDistanceM
= constrainf(rescueState
.sensor
.distanceToHomeM
, GPS_RESCUE_MIN_DESCENT_DIST_M
, gpsRescueConfig()->descentDistanceM
);
244 const float initialAltitudeCm
= gpsRescueConfig()->initialAltitudeM
* 100.0f
;
245 const float rescueAltitudeBufferCm
= gpsRescueConfig()->rescueAltitudeBufferM
* 100.0f
;
246 switch (gpsRescueConfig()->altitudeMode
) {
248 rescueState
.intent
.returnAltitudeCm
= initialAltitudeCm
;
251 rescueState
.intent
.returnAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueAltitudeBufferCm
;
255 rescueState
.intent
.returnAltitudeCm
= rescueState
.sensor
.maxAltitudeCm
+ rescueAltitudeBufferCm
;
261 static void rescueAttainPosition()
263 // runs at 100hz, but only updates RPYT settings when new GPS Data arrives and when not in idle phase.
264 static float previousVelocityError
= 0.0f
;
265 static float velocityI
= 0.0f
;
266 static float previousVelocityD
= 0.0f
; // for smoothing
267 static float previousPitchAdjustment
= 0.0f
;
268 static float throttleI
= 0.0f
;
269 static float previousAltitudeError
= 0.0f
;
270 static int16_t throttleAdjustment
= 0;
272 switch (rescueState
.phase
) {
274 // values to be returned when no rescue is active
275 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
276 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
277 rescueThrottle
= rcCommand
[THROTTLE
];
279 case RESCUE_INITIALIZE
:
280 // Initialize internal variables each time GPS Rescue is started
281 // Note that sensor values can't be initialised here. Use idleTasks() to initialise them.
282 previousVelocityError
= 0.0f
;
284 previousVelocityD
= 0.0f
;
285 previousPitchAdjustment
= 0.0f
;
287 previousAltitudeError
= 0.0f
;
288 throttleAdjustment
= 0;
290 case RESCUE_DO_NOTHING
:
291 gpsRescueAngle
[AI_PITCH
] = 0.0f
;
292 gpsRescueAngle
[AI_ROLL
] = 0.0f
;
293 rescueThrottle
= gpsRescueConfig()->throttleHover
- 50;
300 Altitude (throttle) controller
302 // currentAltitudeCm is updated at TASK_GPS_RATE since GPS initiates updateGPSRescueState()
303 const float altitudeError
= (rescueState
.intent
.targetAltitudeCm
- rescueState
.sensor
.currentAltitudeCm
) * 0.01f
;
304 // height above target in metres (negative means too low)
305 // at the start, the target starts at current altitude plus one step. Increases stepwise to intended value.
308 const float throttleP
= gpsRescueConfig()->throttleP
* altitudeError
;
311 throttleI
+= 0.1f
* gpsRescueConfig()->throttleI
* altitudeError
* rescueState
.sensor
.altitudeDataIntervalSeconds
;
312 throttleI
= constrainf(throttleI
, -1.0f
* GPS_RESCUE_MAX_ITERM_THROTTLE
, 1.0f
* GPS_RESCUE_MAX_ITERM_THROTTLE
);
313 // up to 20% increase in throttle from I alone
315 // D component is error based, so includes positive boost when climbing and negative boost on descent
316 float throttleDRaw
= ((altitudeError
- previousAltitudeError
) / rescueState
.sensor
.altitudeDataIntervalSeconds
);
317 previousAltitudeError
= altitudeError
;
318 throttleDRaw
+= rescueState
.intent
.descentSpeedModifier
* throttleDRaw
;
319 // add up to 2x D when descent rate is faster
321 float throttleD
= pt2FilterApply(&throttleDLpf
, throttleDRaw
);
322 throttleD
= gpsRescueConfig()->throttleD
* throttleD
;
324 // acceleration component not currently implemented - was needed previously due to GPS lag, maybe not needed now.
326 float tiltAdjustment
= 1.0f
- getCosTiltAngle(); // 0 = flat, gets to 0.2 correcting on a windy day
327 tiltAdjustment
*= (gpsRescueConfig()->throttleHover
- 1000);
328 // if hover is 1300, and adjustment .2, this gives us 0.2*300 or 60 of extra throttle, not much, but useful
329 // too much and landings with lots of pitch adjustment, eg windy days, can be a problem
331 throttleAdjustment
= throttleP
+ throttleI
+ throttleD
+ tiltAdjustment
;
333 rescueThrottle
= gpsRescueConfig()->throttleHover
+ throttleAdjustment
;
334 rescueThrottle
= constrainf(rescueThrottle
, gpsRescueConfig()->throttleMin
, gpsRescueConfig()->throttleMax
);
335 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 0, throttleP
);
336 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 1, throttleD
);
339 Heading / yaw controller
341 // directionToHome and distanceToHome are accurate if the GPS Home point is accurate.
342 // attitude.values.yaw is set by imuCalculateEstimatedAttitude() but only updated while groundspeed exceeds 2 m/s
343 // for accurate return, the craft should exceed 5m/s in clean nose-forward flight at some point
344 // the faster the return speed, the more accurate the IMU will be, but the consequences of IMU error at the start are greater
345 // A compass (magnetometer) is vital for accurate GPS rescue at slow speeds
347 // if the quad is pointing 180 degrees wrong at failsafe time, it will take 2s to rotate fully at 90 deg/s max rate
348 // this gives the level mode controller time to adjust pitch and roll during the yaw
349 // we need a relatively gradual trajectory change for attitude.values.yaw to update effectively
351 if (rescueState
.intent
.updateYaw
) {
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
- ABS(rescueYaw
) * 0.01f
, 0.0f
, 1.0f
);
357 // attenuate roll as yaw rate increases, no roll at 100 deg/s of yaw
358 const float rollAdjustment
= - rescueYaw
* gpsRescueConfig()->rollMix
* rollMixAttenuator
;
359 // mix in the desired amount of roll; 1:1 yaw:roll when rollMix = 100 and yaw angles are small
360 // when gpsRescueConfig()->rollMix is zero, there is no roll adjustment
361 // rollAdjustment is degrees * 100
362 // note that the roll element has the same sign as the yaw element *before* GET_DIRECTION
363 gpsRescueAngle
[AI_ROLL
] = constrainf(rollAdjustment
, -rescueState
.intent
.rollAngleLimitDeg
* 100.0f
, rescueState
.intent
.rollAngleLimitDeg
* 100.0f
);
364 // gpsRescueAngle is added to the normal roll Angle Mode corrections in pid.c
366 rescueYaw
*= GET_DIRECTION(rcControlsConfig()->yaw_control_reversed
);
373 Pitch / velocity controller
375 static float pitchAdjustment
= 0.0f
;
376 static float pitchAdjustmentFiltered
= 0.0f
;
379 const float sampleIntervalNormaliseFactor
= rescueState
.sensor
.gpsDataIntervalSeconds
* 10.0f
;
380 const float velocityTargetLimiter
= constrainf((60.0f
-rescueState
.sensor
.absErrorAngle
) / 60.0f
, 0.0f
, 1.0f
);
381 // attenuate velocity target when quad is not pointing towards home
382 // stops attempting to gain velocity when pointing the wrong way, eg overshooting home point (sudden heading error)
383 float velocityError
= (rescueState
.intent
.targetVelocityCmS
* velocityTargetLimiter
- rescueState
.sensor
.velocityToHomeCmS
);
385 // velocityError is in cm per second, positive means too slow.
386 // NB positive pitch setpoint means nose down.
387 // quite heavily smoothed
388 // IdleTasks sets target velocity to current velocity to minimise D spike when starting, and keep error = 0
391 const float velocityP
= velocityError
* gpsRescueConfig()->velP
;
394 velocityI
+= 0.01f
* gpsRescueConfig()->velI
* velocityError
* sampleIntervalNormaliseFactor
;
395 // normalisation increases amount added when data rate is slower than expected
396 velocityI
*= rescueState
.intent
.targetVelocityCmS
/ rescueState
.intent
.targetVelocityCmS
;
397 // attenuate iTerm at slower target velocity, to minimise overshoot, mostly during deceleration to landing phase
398 velocityI
= constrainf(velocityI
, -1.0f
* GPS_RESCUE_MAX_ITERM_VELOCITY
, 1.0f
* GPS_RESCUE_MAX_ITERM_VELOCITY
);
399 // I component alone cannot exceed a pitch angle of 10%
402 float velocityD
= ((velocityError
- previousVelocityError
) / sampleIntervalNormaliseFactor
);
403 previousVelocityError
= velocityError
;
404 // simple first order filter on derivative with k = 0.5 for 200ms steps
405 velocityD
= previousVelocityD
+ rescueState
.sensor
.filterK
* (velocityD
- previousVelocityD
);
406 previousVelocityD
= velocityD
;
407 velocityD
*= gpsRescueConfig()->velD
;
409 // Pitch PIDsum and smoothing
410 pitchAdjustment
= velocityP
+ velocityD
+ velocityI
;
411 // simple rate of change limiter - not more than 25 deg/s of pitch change to keep pitch smooth
412 float pitchAdjustmentDelta
= pitchAdjustment
- previousPitchAdjustment
;
413 if (pitchAdjustmentDelta
> rescueState
.sensor
.maxPitchStep
) {
414 pitchAdjustment
= previousPitchAdjustment
+ rescueState
.sensor
.maxPitchStep
;
415 } else if (pitchAdjustmentDelta
< -rescueState
.sensor
.maxPitchStep
) {
416 pitchAdjustment
= previousPitchAdjustment
- rescueState
.sensor
.maxPitchStep
;
418 const float movingAvgPitchAdjustment
= 0.5f
* (previousPitchAdjustment
+ pitchAdjustment
);
419 // moving average seems to work best here, a lot of sequential up and down in velocity data
420 previousPitchAdjustment
= pitchAdjustment
;
421 pitchAdjustment
= movingAvgPitchAdjustment
;
422 // pitchAdjustment is the absolute Pitch angle adjustment value in degrees * 100
423 // it gets added to the normal level mode Pitch adjustments in pid.c
425 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 0, velocityP
);
426 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 1, velocityD
);
427 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 3, rescueState
.intent
.targetVelocityCmS
);
428 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 1, rescueState
.intent
.targetVelocityCmS
);
431 pitchAdjustmentFiltered
= pt3FilterApply(&pitchLpf
, pitchAdjustment
);
432 // upsampling smoothing of steps in pitch angle
434 gpsRescueAngle
[AI_PITCH
] = constrainf(pitchAdjustmentFiltered
, -rescueState
.intent
.pitchAngleLimitDeg
* 100.0f
, rescueState
.intent
.pitchAngleLimitDeg
* 100.0f
);
435 // this angle gets added to the normal pitch Angle Mode control values in pid.c - will be seen in pitch setpoint
437 DEBUG_SET(DEBUG_RTH
, 0, gpsRescueAngle
[AI_PITCH
]);
441 static void performSanityChecks()
443 static timeUs_t previousTimeUs
= 0; // Last time Stalled/LowSat was checked
444 static float prevAltitudeCm
= 0.0f
; // to calculate ascent or descent change
445 static float prevTargetAltitudeCm
= 0.0f
; // to calculate ascent or descent target change
446 static int8_t secondsLowSats
= 0; // Minimum sat detection
447 static int8_t secondsDoingNothing
= 0; // Limit on doing nothing
448 const timeUs_t currentTimeUs
= micros();
450 if (rescueState
.phase
== RESCUE_IDLE
) {
451 rescueState
.failure
= RESCUE_HEALTHY
;
453 } else if (rescueState
.phase
== RESCUE_INITIALIZE
) {
454 // Initialize internal variables each time GPS Rescue is started
455 previousTimeUs
= currentTimeUs
;
456 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
457 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
458 secondsLowSats
= 5; // Start the count at 5 to be less forgiving at the beginning
459 secondsDoingNothing
= 0;
463 // Handle rescue failures. Don't disarm for rescue failure during stick induced rescues.
464 const bool hardFailsafe
= !rxIsReceivingSignal();
465 if (rescueState
.failure
!= RESCUE_HEALTHY
) {
466 if (gpsRescueConfig()->sanityChecks
== RESCUE_SANITY_ON
) {
467 rescueState
.phase
= RESCUE_ABORT
;
468 } else if ((gpsRescueConfig()->sanityChecks
== RESCUE_SANITY_FS_ONLY
) && hardFailsafe
) {
469 rescueState
.phase
= RESCUE_ABORT
;
471 rescueState
.phase
= RESCUE_DO_NOTHING
;
475 // If crash recovery is triggered during a rescue, immediately disarm, ignoring the crash flip recovery settings.
476 if (crashRecoveryModeActive()) {
477 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
478 disarm(DISARM_REASON_CRASH_PROTECTION
);
482 // Check if GPS comms are healthy
483 if (!rescueState
.sensor
.healthy
) {
484 rescueState
.failure
= RESCUE_GPSLOST
;
486 // Things that should run at a low refresh rate (such as flyaway detection, etc)
488 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
489 if (dTime
< 1000000) { //1hz
492 previousTimeUs
= currentTimeUs
;
494 if (rescueState
.phase
== RESCUE_FLY_HOME
) {
495 rescueState
.intent
.secondsFailing
+= (rescueState
.sensor
.velocityToHomeCmS
< 0.5 * rescueState
.intent
.targetVelocityCmS
) ? 1 : -1;
496 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 20);
497 if (rescueState
.intent
.secondsFailing
== 20) {
499 //If there is a mag and has not been disabled, we have to assume is healthy and has been used in imu.c
500 if (sensors(SENSOR_MAG
) && gpsRescueConfig()->useMag
&& !magForceDisable
) {
501 //Try again with mag disabled
502 magForceDisable
= true;
503 rescueState
.intent
.secondsFailing
= 0;
507 rescueState
.failure
= RESCUE_STALLED
;
512 // These conditions are 'special', in that even with sanity checks off, they should still apply
513 const float actualAltitudeChange
= rescueState
.sensor
.currentAltitudeCm
- prevAltitudeCm
;
514 const float targetAltitudeChange
= rescueState
.intent
.targetAltitudeCm
- prevTargetAltitudeCm
;
515 const float ratio
= actualAltitudeChange
/ targetAltitudeChange
;
516 if (rescueState
.phase
== RESCUE_ATTAIN_ALT
) {
517 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
518 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
519 if (rescueState
.intent
.secondsFailing
== 10) {
521 rescueState
.phase
= RESCUE_ABORT
;
522 // if stuck in a tree while climbing, or otherwise can't climb, stop motors and disarm
525 } else if (rescueState
.phase
== RESCUE_LANDING
|| rescueState
.phase
== RESCUE_DESCENT
) {
526 rescueState
.intent
.secondsFailing
+= ratio
> 0.5f
? -1 : 1;
527 rescueState
.intent
.secondsFailing
= constrain(rescueState
.intent
.secondsFailing
, 0, 10);
528 if (rescueState
.intent
.secondsFailing
== 10) {
530 rescueState
.phase
= RESCUE_ABORT
;
531 // if stuck in a tree while climbing, or don't disarm on impact, or enable GPS rescue on the ground too close
534 } else if (rescueState
.phase
== RESCUE_DO_NOTHING
) {
535 secondsDoingNothing
= MIN(secondsDoingNothing
+ 1, 10);
536 if (secondsDoingNothing
== 10) {
537 rescueState
.phase
= RESCUE_ABORT
;
538 // prevent indefinite flyaways when sanity checks are off, and
539 // time limit the "do nothing" period when a switch initiated failsafe fails sanity checks
540 // this is controversial
543 prevAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
;
544 prevTargetAltitudeCm
= rescueState
.intent
.targetAltitudeCm
;
546 secondsLowSats
+= gpsSol
.numSat
< (gpsConfig()->gpsMinimumSats
) ? 1 : -1;
547 secondsLowSats
= constrain(secondsLowSats
, 0, 10);
549 if (secondsLowSats
== 10) {
550 rescueState
.failure
= RESCUE_LOWSATS
;
553 DEBUG_SET(DEBUG_RTH
, 2, rescueState
.failure
);
554 DEBUG_SET(DEBUG_RTH
, 3, (rescueState
.intent
.secondsFailing
* 100 + secondsLowSats
)); //Failure can change with no new GPS Data
557 static void sensorUpdate()
559 static float prevDistanceToHomeCm
= 0.0f
;
560 const timeUs_t currentTimeUs
= micros();
562 static timeUs_t previousAltitudeDataTimeUs
= 0;
563 const timeDelta_t altitudeDataIntervalUs
= cmpTimeUs(currentTimeUs
, previousAltitudeDataTimeUs
);
564 rescueState
.sensor
.altitudeDataIntervalSeconds
= altitudeDataIntervalUs
* 0.000001f
;
565 previousAltitudeDataTimeUs
= currentTimeUs
;
567 rescueState
.sensor
.currentAltitudeCm
= getAltitude();
569 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 2, rescueState
.sensor
.currentAltitudeCm
);
570 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 2, rescueState
.sensor
.currentAltitudeCm
);
571 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 0, rescueState
.sensor
.groundSpeedCmS
); // groundspeed cm/s
572 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 1, gpsSol
.groundCourse
); // degrees * 10
573 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 2, attitude
.values
.yaw
); // degrees * 10
574 DEBUG_SET(DEBUG_GPS_RESCUE_HEADING
, 3, rescueState
.sensor
.directionToHome
); // degrees * 10
576 rescueState
.sensor
.healthy
= gpsIsHealthy();
578 if (rescueState
.phase
== RESCUE_LANDING
) {
579 // do this at sensor update rate, not the much slower GPS rate, for quick disarm
580 rescueState
.sensor
.accMagnitude
= (float) sqrtf(sq(acc
.accADC
[Z
]) + sq(acc
.accADC
[X
]) + sq(acc
.accADC
[Y
])) * acc
.dev
.acc_1G_rec
;
583 rescueState
.sensor
.directionToHome
= GPS_directionToHome
;
584 rescueState
.sensor
.errorAngle
= (attitude
.values
.yaw
- rescueState
.sensor
.directionToHome
) * 0.1f
;
585 // both attitude and direction are in degrees * 10, errorAngle is degrees
586 if (rescueState
.sensor
.errorAngle
<= -180) {
587 rescueState
.sensor
.errorAngle
+= 360;
588 } else if (rescueState
.sensor
.errorAngle
> 180) {
589 rescueState
.sensor
.errorAngle
-= 360;
591 rescueState
.sensor
.absErrorAngle
= fabsf(rescueState
.sensor
.errorAngle
);
597 rescueState
.sensor
.distanceToHomeCm
= GPS_distanceToHomeCm
;
598 rescueState
.sensor
.distanceToHomeM
= rescueState
.sensor
.distanceToHomeCm
/ 100.0f
;
599 rescueState
.sensor
.groundSpeedCmS
= gpsSol
.groundSpeed
; // cm/s
602 static timeUs_t previousGPSDataTimeUs
= 0;
603 const timeDelta_t gpsDataIntervalUs
= cmpTimeUs(currentTimeUs
, previousGPSDataTimeUs
);
604 rescueState
.sensor
.gpsDataIntervalSeconds
= constrainf(gpsDataIntervalUs
* 0.000001f
, 0.01f
, 1.0f
);
605 // Range from 10ms (100hz) to 1000ms (1Hz). Intended to cover common GPS data rates and exclude unusual values.
606 previousGPSDataTimeUs
= currentTimeUs
;
608 rescueState
.sensor
.filterK
= pt1FilterGain(0.8, rescueState
.sensor
.gpsDataIntervalSeconds
);
609 // 0.8341 for 1hz, 0.5013 for 5hz, 0.3345 for 10hz, 0.1674 for 25Hz, etc
611 rescueState
.sensor
.velocityToHomeCmS
= (prevDistanceToHomeCm
- rescueState
.sensor
.distanceToHomeCm
) / rescueState
.sensor
.gpsDataIntervalSeconds
;
612 // positive = towards home. First value is useless since prevDistanceToHomeCm was zero.
613 prevDistanceToHomeCm
= rescueState
.sensor
.distanceToHomeCm
;
615 rescueState
.sensor
.maxPitchStep
= rescueState
.sensor
.gpsDataIntervalSeconds
* GPS_RESCUE_MAX_PITCH_RATE
;
617 DEBUG_SET(DEBUG_GPS_RESCUE_VELOCITY
, 2, rescueState
.sensor
.velocityToHomeCmS
);
618 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 0, rescueState
.sensor
.velocityToHomeCmS
);
622 // This function flashes "RESCUE N/A" in the OSD if:
623 // 1. sensor healthy - GPS data is being received.
624 // 2. GPS has a 3D fix.
625 // 3. GPS number of satellites is greater than or equal to the minimum configured satellite count.
626 // Note 1: cannot arm without the required number of sats;
627 // hence this flashing indicates that after having enough sats, we now have below the minimum and the rescue likely would fail
628 // Note 2: this function does not take into account the distance from homepoint etc. (gps_rescue_min_dth).
629 // The sanity checks are independent, this just provides the OSD warning
630 static bool checkGPSRescueIsAvailable(void)
632 static timeUs_t previousTimeUs
= 0; // Last time LowSat was checked
633 const timeUs_t currentTimeUs
= micros();
634 static int8_t secondsLowSats
= 0; // Minimum sat detection
635 static bool lowsats
= false;
636 static bool noGPSfix
= false;
639 if (!gpsIsHealthy() || !STATE(GPS_FIX_HOME
)) {
643 // Things that should run at a low refresh rate >> ~1hz
644 const timeDelta_t dTime
= cmpTimeUs(currentTimeUs
, previousTimeUs
);
645 if (dTime
< 1000000) { //1hz
646 if (noGPSfix
|| lowsats
) {
652 previousTimeUs
= currentTimeUs
;
654 if (!STATE(GPS_FIX
)) {
661 secondsLowSats
= constrain(secondsLowSats
+ ((gpsSol
.numSat
< gpsConfig()->gpsMinimumSats
) ? 1 : -1), 0, 2);
662 if (secondsLowSats
== 2) {
672 void disarmOnImpact(void)
674 if (rescueState
.sensor
.accMagnitude
> 3.0f
) {
675 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
676 disarm(DISARM_REASON_GPS_RESCUE
);
683 rescueState
.intent
.altitudeStep
= -1.0f
* rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
684 // adjust altitude step for interval between altitude readings
686 const float descentAttenuator
= rescueState
.intent
.returnAltitudeCm
/ 2000.0f
;
687 // descend more slowly if return altitude is lower than 20m
688 if (descentAttenuator
< 1.0f
) {
689 rescueState
.intent
.altitudeStep
*= descentAttenuator
;
692 rescueState
.intent
.descentSpeedModifier
= constrainf(rescueState
.sensor
.currentAltitudeCm
/ 4000.0f
, 0.0f
, 1.0f
);
693 rescueState
.intent
.targetAltitudeCm
+= rescueState
.intent
.altitudeStep
* (1.0f
+ (2.0f
* rescueState
.intent
.descentSpeedModifier
));
694 // increase descent rate to max of 3x default above 40m, 2x above 20m, 1.166 at 5m, default at ground level.
697 void altitudeAchieved(void)
699 rescueState
.intent
.targetAltitudeCm
= rescueState
.intent
.returnAltitudeCm
;
700 rescueState
.intent
.altitudeStep
= 0;
701 rescueState
.intent
.updateYaw
= true; // allow yaw updating
702 rescueState
.phase
= RESCUE_ROTATE
;
705 void updateGPSRescueState(void)
706 // this runs a lot faster than the GPS Data update rate, and runs whether or not rescue is active
708 if (!FLIGHT_MODE(GPS_RESCUE_MODE
)) {
709 rescueStop(); // sets phase to idle; does nothing else. Idle tasks still run.
710 } else if (FLIGHT_MODE(GPS_RESCUE_MODE
) && rescueState
.phase
== RESCUE_IDLE
) {
711 rescueStart(); // sets phase to rescue_initialise if we enter GPS Rescue mode while idle
712 rescueAttainPosition(); // Initialise basic parameters when a Rescue starts (can't initialise sensor data reliably)
713 performSanityChecks(); // Initialises sanity check values when a Rescue starts
716 // Will now be in RESCUE_INITIALIZE mode, if just entered Rescue while IDLE, otherwise stays IDLE
718 sensorUpdate(); // always get latest GPS / Altitude data; update ascend and descend rates
720 uint8_t halfAngle
= gpsRescueConfig()->angle
/ 2;
721 float proximityToLandingArea
= 0.0f
;
722 bool startedLow
= true;
723 rescueState
.isAvailable
= checkGPSRescueIsAvailable();
726 switch (rescueState
.phase
) {
728 // in Idle phase = NOT in GPS Rescue
729 // set maxAltitude for flight
730 // update the return altitude and descent distance values, to have valid settings immediately they are needed
731 // initialise the target altitude and velocity to current values to minimise D spike on startup
734 // sanity checks are bypassed in IDLE mode; instead, failure state is always initialised to HEALTHY
735 // target altitude is always set to current altitude.
737 case RESCUE_INITIALIZE
:
738 // Things that should abort the start of a Rescue
739 if (!STATE(GPS_FIX_HOME
)) {
740 // we didn't get a home point on arming
741 rescueState
.failure
= RESCUE_NO_HOME_POINT
;
742 // will result in a disarm via the sanity check system, with delay if switch induced
743 // alternative is to prevent the rescue by returning to IDLE, but this could cause flyaways
744 } else if (rescueState
.sensor
.distanceToHomeM
< gpsRescueConfig()->minRescueDth
) {
745 // Attempt to initiate inside minimum activation distance -> landing mode
746 rescueState
.intent
.altitudeStep
= -rescueState
.sensor
.altitudeDataIntervalSeconds
* gpsRescueConfig()->descendRate
;
747 rescueState
.intent
.targetVelocityCmS
= 0; // zero forward velocity
748 rescueState
.intent
.pitchAngleLimitDeg
= 0; // flat on pitch
749 rescueState
.intent
.rollAngleLimitDeg
= 0; // flat on roll also
750 rescueState
.intent
.targetAltitudeCm
= rescueState
.sensor
.currentAltitudeCm
+ rescueState
.intent
.altitudeStep
;
751 rescueState
.phase
= RESCUE_LANDING
;
752 // start landing from current altitude
754 rescueState
.phase
= RESCUE_ATTAIN_ALT
;
755 rescueState
.intent
.secondsFailing
= 0; // reset the sanity check timer for the climb
756 rescueState
.intent
.targetLandingAltitudeCm
= 100.0f
* gpsRescueConfig()->targetLandingAltitudeM
;
757 startedLow
= (rescueState
.sensor
.currentAltitudeCm
<= rescueState
.intent
.returnAltitudeCm
);
758 rescueState
.intent
.updateYaw
= false; // point the nose to home at all times during the rescue
759 rescueState
.intent
.targetVelocityCmS
= 0; // zero forward velocity while climbing
760 rescueState
.intent
.pitchAngleLimitDeg
= 0; // flat on pitch
761 rescueState
.intent
.rollAngleLimitDeg
= 0; // flat on roll also
762 rescueState
.intent
.altitudeStep
= 0;
763 rescueState
.intent
.descentSpeedModifier
= 0.0f
;
764 rescueState
.intent
.yawAttenuator
= 0.0f
;
768 case RESCUE_ATTAIN_ALT
:
769 // gradually increment the target altitude until the final target altitude value is set
770 // also require that the final target altitude has been achieved before moving on
771 // sanity check will abort if altitude gain is blocked for a cumulative period
772 // TO DO: if overshoots are a problem after craft achieves target altitude changes, adjust termination threshold with current vertical velocity
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 // gradually acquire yaw and roll authority over 50 iterations, or about half a second
791 if (rescueState
.intent
.yawAttenuator
< 1.0f
) {
792 rescueState
.intent
.yawAttenuator
+= 0.02f
;
795 if (rescueState
.sensor
.absErrorAngle
< 90.0f
) {
796 const float angleToHome
= 1.0f
- (rescueState
.sensor
.absErrorAngle
/ 90.0f
);
797 // once , gradually increase target velocity and roll angle
798 rescueState
.intent
.targetVelocityCmS
= gpsRescueConfig()->rescueGroundspeed
* angleToHome
;
799 rescueState
.intent
.pitchAngleLimitDeg
= gpsRescueConfig()->angle
* angleToHome
;
800 rescueState
.intent
.rollAngleLimitDeg
= gpsRescueConfig()->angle
* angleToHome
;
801 if (rescueState
.sensor
.absErrorAngle
< 10.0f
) {
802 // enter fly home phase with full forward velocity target and full angle values
803 rescueState
.phase
= RESCUE_FLY_HOME
;
804 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for flight home
805 rescueState
.intent
.yawAttenuator
= 1.0f
;
806 rescueState
.intent
.targetVelocityCmS
= gpsRescueConfig()->rescueGroundspeed
;
807 rescueState
.intent
.pitchAngleLimitDeg
= gpsRescueConfig()->angle
;
808 rescueState
.intent
.rollAngleLimitDeg
= gpsRescueConfig()->angle
;
813 case RESCUE_FLY_HOME
:
814 // fly home with full control on all axes, pitching forward to gain speed
816 if (rescueState
.sensor
.distanceToHomeM
<= rescueState
.intent
.descentDistanceM
) {
817 rescueState
.phase
= RESCUE_DESCENT
;
818 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for descent
824 // attenuate velocity and altitude targets while updating the heading to home
825 // once inside the landing box, stop rotating, just descend
826 if (rescueState
.sensor
.currentAltitudeCm
< rescueState
.intent
.targetLandingAltitudeCm
) {
827 // enter landing mode once below landing altitude
828 rescueState
.phase
= RESCUE_LANDING
;
829 rescueState
.intent
.secondsFailing
= 0; // reset sanity timer for landing
830 rescueState
.intent
.targetVelocityCmS
= 0; // zero velocity to home
831 rescueState
.intent
.pitchAngleLimitDeg
= halfAngle
; // reduced pitch angles
832 rescueState
.intent
.rollAngleLimitDeg
= 0; // no roll while landing
835 const float distanceToLandingAreaM
= rescueState
.sensor
.distanceToHomeM
- 2.0f
;
836 // considers home to be a 2m circle around home to avoid overshooting home point
837 proximityToLandingArea
= constrainf(distanceToLandingAreaM
/ rescueState
.intent
.descentDistanceM
, 0.0f
, 1.0f
);
838 rescueState
.intent
.targetVelocityCmS
= gpsRescueConfig()->rescueGroundspeed
* proximityToLandingArea
;
839 // reduce target velocity as we get closer to home. Zero within 2m of home, reducing risk of overshooting.
840 // if quad drifts further than 2m away from home, should by then have rotated towards home, and pitch is allowed
841 rescueState
.intent
.rollAngleLimitDeg
= gpsRescueConfig()->angle
* proximityToLandingArea
;
842 // reduce roll capability when closer to home, none within final 2m
848 // control yaw angle, throttle and pitch. no roll. descend steadily until impact, then disarm.
853 case RESCUE_COMPLETE
:
858 setArmingDisabled(ARMING_DISABLED_ARM_SWITCH
);
859 disarm(DISARM_REASON_GPS_RESCUE
);
863 case RESCUE_DO_NOTHING
:
871 DEBUG_SET(DEBUG_GPS_RESCUE_TRACKING
, 3, rescueState
.intent
.targetAltitudeCm
);
872 DEBUG_SET(DEBUG_GPS_RESCUE_THROTTLE_PID
, 3, rescueState
.intent
.targetAltitudeCm
);
873 DEBUG_SET(DEBUG_RTH
, 1, rescueState
.phase
);
875 performSanityChecks();
876 rescueAttainPosition();
881 float gpsRescueGetYawRate(void)
886 float gpsRescueGetThrottle(void)
888 // Calculated a desired commanded throttle scaled from 0.0 to 1.0 for use in the mixer.
889 // We need to compensate for min_check since the throttle value set by gps rescue
890 // is based on the raw rcCommand value commanded by the pilot.
891 float commandedThrottle
= scaleRangef(rescueThrottle
, MAX(rxConfig()->mincheck
, PWM_RANGE_MIN
), PWM_RANGE_MAX
, 0.0f
, 1.0f
);
892 commandedThrottle
= constrainf(commandedThrottle
, 0.0f
, 1.0f
);
894 return commandedThrottle
;
897 bool gpsRescueIsConfigured(void)
899 return failsafeConfig()->failsafe_procedure
== FAILSAFE_PROCEDURE_GPS_RESCUE
|| isModeActivationConditionPresent(BOXGPSRESCUE
);
902 bool gpsRescueIsAvailable(void)
904 return rescueState
.isAvailable
;
907 bool gpsRescueIsDisabled(void)
908 // used for OSD warning
910 return (!STATE(GPS_FIX_HOME
));
914 bool gpsRescueDisableMag(void)
916 return ((!gpsRescueConfig()->useMag
|| magForceDisable
) && (rescueState
.phase
>= RESCUE_INITIALIZE
) && (rescueState
.phase
<= RESCUE_LANDING
));