2 #include "build/debug.h"
4 #include "common/maths.h"
5 #include "common/utils.h"
8 #include "fc/fc_core.h"
9 #include "fc/runtime_config.h"
11 #include "flight/imu.h"
12 #include "flight/mixer.h"
13 #include "flight/wind_estimator.h"
15 #include "navigation/navigation.h"
17 #include "sensors/battery.h"
21 #if defined(USE_ADC) && defined(USE_GPS)
25 * - horizontalWindSpeed
26 * - windHeading degrees
28 * returns same unit as horizontalWindSpeed
30 static float forwardWindSpeed(float heading
, float horizontalWindSpeed
, float windHeading
) {
31 return horizontalWindSpeed
* cos_approx(DEGREES_TO_RADIANS(windHeading
- heading
));
34 #ifdef USE_WIND_ESTIMATOR
36 * - forwardSpeed (same unit as horizontalWindSpeed)
38 * - horizontalWindSpeed (same unit as forwardSpeed)
39 * - windHeading degrees
43 static float windDriftCompensationAngle(float forwardSpeed
, float heading
, float horizontalWindSpeed
, float windHeading
) {
44 return RADIANS_TO_DEGREES(asin_approx(-horizontalWindSpeed
* sin_approx(DEGREES_TO_RADIANS(windHeading
- heading
)) / forwardSpeed
));
48 * - forwardSpeed (same unit as horizontalWindSpeed)
50 * - horizontalWindSpeed (same unit as forwardSpeed)
51 * - windHeading degrees
53 * returns (same unit as forwardSpeed and horizontalWindSpeed)
55 static float windDriftCorrectedForwardSpeed(float forwardSpeed
, float heading
, float horizontalWindSpeed
, float windHeading
) {
56 return forwardSpeed
* cos_approx(DEGREES_TO_RADIANS(windDriftCompensationAngle(forwardSpeed
, heading
, horizontalWindSpeed
, windHeading
)));
60 * - forwardSpeed (same unit as horizontalWindSpeed)
62 * - horizontalWindSpeed (same unit as forwardSpeed)
63 * - windHeading degrees
65 * returns (same unit as forwardSpeed and horizontalWindSpeed)
67 static float windCompensatedForwardSpeed(float forwardSpeed
, float heading
, float horizontalWindSpeed
, float windHeading
) {
68 return windDriftCorrectedForwardSpeed(forwardSpeed
, heading
, horizontalWindSpeed
, windHeading
) + forwardWindSpeed(heading
, horizontalWindSpeed
, windHeading
);
73 static int8_t RTHInitialAltitudeChangePitchAngle(float altitudeChange
) {
74 return altitudeChange
< 0 ? navConfig()->fw
.max_dive_angle
: -navConfig()->fw
.max_climb_angle
;
79 static float estimatePitchPower(float pitch
) {
80 int16_t altitudeChangeThrottle
= fixedWingPitchToThrottleCorrection(DEGREES_TO_DECIDEGREES(pitch
));
81 altitudeChangeThrottle
= constrain(altitudeChangeThrottle
, navConfig()->fw
.min_throttle
, navConfig()->fw
.max_throttle
);
82 const float altitudeChangeThrToCruiseThrRatio
= (float)(altitudeChangeThrottle
- getThrottleIdleValue()) / (navConfig()->fw
.cruise_throttle
- getThrottleIdleValue());
83 return (float)heatLossesCompensatedPower(batteryMetersConfig()->idle_power
+ batteryMetersConfig()->cruise_power
* altitudeChangeThrToCruiseThrRatio
) / 100;
86 // altitudeChange is in m
87 // verticalWindSpeed is in m/s
88 // cruise_speed is in cm/s
89 // output is in seconds
90 static float estimateRTHAltitudeChangeTime(float altitudeChange
, float verticalWindSpeed
) {
91 // Assuming increase in throttle keeps air speed at cruise speed
92 const float estimatedVerticalSpeed
= (float)navConfig()->fw
.cruise_speed
/ 100 * sin_approx(DEGREES_TO_RADIANS(RTHInitialAltitudeChangePitchAngle(altitudeChange
))) + verticalWindSpeed
;
93 return altitudeChange
/ estimatedVerticalSpeed
;
96 // altitudeChange is in m
97 // horizontalWindSpeed is in m/s
98 // windHeading is in degrees
99 // verticalWindSpeed is in m/s
100 // cruise_speed is in cm/s
101 // output is in meters
102 static float estimateRTHAltitudeChangeGroundDistance(float altitudeChange
, float horizontalWindSpeed
, float windHeading
, float verticalWindSpeed
) {
103 // Assuming increase in throttle keeps air speed at cruise speed
104 const float estimatedHorizontalSpeed
= (float)navConfig()->fw
.cruise_speed
/ 100 * cos_approx(DEGREES_TO_RADIANS(RTHInitialAltitudeChangePitchAngle(altitudeChange
))) + forwardWindSpeed(DECIDEGREES_TO_DEGREES((float)attitude
.values
.yaw
), horizontalWindSpeed
, windHeading
);
105 return estimateRTHAltitudeChangeTime(altitudeChange
, verticalWindSpeed
) * estimatedHorizontalSpeed
;
108 // altitudeChange is in m
109 // verticalWindSpeed is in m/s
111 static float estimateRTHInitialAltitudeChangeEnergy(float altitudeChange
, float verticalWindSpeed
) {
112 const float RTHInitialAltitudeChangePower
= estimatePitchPower(altitudeChange
< 0 ? navConfig()->fw
.max_dive_angle
: -navConfig()->fw
.max_climb_angle
);
113 return RTHInitialAltitudeChangePower
* estimateRTHAltitudeChangeTime(altitudeChange
, verticalWindSpeed
) / 3600;
116 // horizontalWindSpeed is in m/s
117 // windHeading is in degrees
118 // verticalWindSpeed is in m/s
119 // altitudeChange is in m
120 // returns distance in m
121 // *heading is in degrees
122 static float estimateRTHDistanceAndHeadingAfterAltitudeChange(float altitudeChange
, float horizontalWindSpeed
, float windHeading
, float verticalWindSpeed
, float *heading
) {
123 float estimatedAltitudeChangeGroundDistance
= estimateRTHAltitudeChangeGroundDistance(altitudeChange
, horizontalWindSpeed
, windHeading
, verticalWindSpeed
);
124 if (navConfig()->general
.flags
.rth_climb_first
&& (altitudeChange
> 0)) {
125 float headingDiff
= DEGREES_TO_RADIANS(DECIDEGREES_TO_DEGREES((float)attitude
.values
.yaw
) - GPS_directionToHome
);
126 float triangleAltitude
= GPS_distanceToHome
* sin_approx(headingDiff
);
127 float triangleAltitudeToReturnStart
= estimatedAltitudeChangeGroundDistance
- GPS_distanceToHome
* cos_approx(headingDiff
);
128 const float reverseHeadingDiff
= RADIANS_TO_DEGREES(atan2_approx(triangleAltitude
, triangleAltitudeToReturnStart
));
129 *heading
= CENTIDEGREES_TO_DEGREES(wrap_36000(DEGREES_TO_CENTIDEGREES(180 + reverseHeadingDiff
+ DECIDEGREES_TO_DEGREES((float)attitude
.values
.yaw
))));
130 return sqrt(sq(triangleAltitude
) + sq(triangleAltitudeToReturnStart
));
132 *heading
= GPS_directionToHome
;
133 return GPS_distanceToHome
- estimatedAltitudeChangeGroundDistance
;
137 // distanceToHome is in meters
139 static float estimateRTHEnergyAfterInitialClimb(float distanceToHome
, float speedToHome
) {
140 const float timeToHome
= distanceToHome
/ speedToHome
; // seconds
141 const float altitudeChangeDescentToHome
= CENTIMETERS_TO_METERS(navConfig()->general
.flags
.rth_alt_control_mode
== NAV_RTH_AT_LEAST_ALT_LINEAR_DESCENT
? MAX(0, getEstimatedActualPosition(Z
) - getFinalRTHAltitude()) : 0);
142 const float pitchToHome
= MIN(RADIANS_TO_DEGREES(atan2_approx(altitudeChangeDescentToHome
, distanceToHome
)), navConfig()->fw
.max_dive_angle
);
143 return estimatePitchPower(pitchToHome
) * timeToHome
/ 3600;
147 static float calculateRemainingEnergyBeforeRTH(bool takeWindIntoAccount
) {
148 // Fixed wing only for now
149 if (!STATE(FIXED_WING_LEGACY
))
152 if (!(feature(FEATURE_VBAT
) && feature(FEATURE_CURRENT_METER
) && navigationPositionEstimateIsHealthy() && (batteryMetersConfig()->cruise_power
> 0) && (ARMING_FLAG(ARMED
)) && ((!STATE(FIXED_WING_LEGACY
)) || (isNavLaunchEnabled() && isFixedWingLaunchDetected())) && (navConfig()->fw
.cruise_speed
> 0) && (currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MWH
) && (currentBatteryProfile
->capacity
.value
> 0) && batteryWasFullWhenPluggedIn() && isImuHeadingValid()
153 #ifdef USE_WIND_ESTIMATOR
154 && isEstimatedWindSpeedValid()
159 const float RTH_initial_altitude_change
= MAX(0, (getFinalRTHAltitude() - getEstimatedActualPosition(Z
)) / 100);
161 float RTH_heading
; // degrees
162 #ifdef USE_WIND_ESTIMATOR
163 uint16_t windHeading
; // centidegrees
164 const float horizontalWindSpeed
= takeWindIntoAccount
? getEstimatedHorizontalWindSpeed(&windHeading
) / 100 : 0; // m/s
165 const float windHeadingDegrees
= CENTIDEGREES_TO_DEGREES((float)windHeading
);
166 const float verticalWindSpeed
= getEstimatedWindSpeed(Z
) / 100;
168 const float RTH_distance
= estimateRTHDistanceAndHeadingAfterAltitudeChange(RTH_initial_altitude_change
, horizontalWindSpeed
, windHeadingDegrees
, verticalWindSpeed
, &RTH_heading
);
169 const float RTH_speed
= windCompensatedForwardSpeed((float)navConfig()->fw
.cruise_speed
/ 100, RTH_heading
, horizontalWindSpeed
, windHeadingDegrees
);
171 UNUSED(takeWindIntoAccount
);
172 const float RTH_distance
= estimateRTHDistanceAndHeadingAfterAltitudeChange(RTH_initial_altitude_change
, 0, 0, 0, &RTH_heading
);
173 const float RTH_speed
= (float)navConfig()->fw
.cruise_speed
/ 100;
176 DEBUG_SET(DEBUG_REM_FLIGHT_TIME
, 0, lrintf(RTH_initial_altitude_change
* 100));
177 DEBUG_SET(DEBUG_REM_FLIGHT_TIME
, 1, lrintf(RTH_distance
* 100));
178 DEBUG_SET(DEBUG_REM_FLIGHT_TIME
, 2, lrintf(RTH_speed
* 100));
179 #ifdef USE_WIND_ESTIMATOR
180 DEBUG_SET(DEBUG_REM_FLIGHT_TIME
, 3, lrintf(horizontalWindSpeed
* 100));
184 return -2; // wind is too strong to return at cruise throttle (TODO: might be possible to take into account min speed thr boost)
186 #ifdef USE_WIND_ESTIMATOR
187 const float energy_to_home
= estimateRTHInitialAltitudeChangeEnergy(RTH_initial_altitude_change
, verticalWindSpeed
) + estimateRTHEnergyAfterInitialClimb(RTH_distance
, RTH_speed
); // Wh
189 const float energy_to_home
= estimateRTHInitialAltitudeChangeEnergy(RTH_initial_altitude_change
, 0) + estimateRTHEnergyAfterInitialClimb(RTH_distance
, RTH_speed
); // Wh
191 const float energy_margin_abs
= (currentBatteryProfile
->capacity
.value
- currentBatteryProfile
->capacity
.critical
) * batteryMetersConfig()->rth_energy_margin
/ 100000; // Wh
192 const float remaining_energy_before_rth
= getBatteryRemainingCapacity() / 1000 - energy_margin_abs
- energy_to_home
; // Wh
194 if (remaining_energy_before_rth
< 0) // No energy left = No time left
197 return remaining_energy_before_rth
;
201 float calculateRemainingFlightTimeBeforeRTH(bool takeWindIntoAccount
) {
203 const float remainingEnergyBeforeRTH
= calculateRemainingEnergyBeforeRTH(takeWindIntoAccount
);
205 // error: return error code directly
206 if (remainingEnergyBeforeRTH
< 0)
207 return remainingEnergyBeforeRTH
;
209 const float averagePower
= (float)calculateAveragePower() / 100;
211 if (averagePower
== 0)
214 const float time_before_rth
= remainingEnergyBeforeRTH
* 3600 / averagePower
;
216 return time_before_rth
;
220 float calculateRemainingDistanceBeforeRTH(bool takeWindIntoAccount
) {
222 const float remainingFlightTimeBeforeRTH
= calculateRemainingFlightTimeBeforeRTH(takeWindIntoAccount
);
224 // error: return error code directly
225 if (remainingFlightTimeBeforeRTH
< 0)
226 return remainingFlightTimeBeforeRTH
;
228 return remainingFlightTimeBeforeRTH
* calculateAverageSpeed();