Create release.yml
[betaflight.git] / src / main / osd / osd_warnings.c
blob4ee43ef614c7e0021e45bf65806beac92dac6e62
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <math.h>
28 #include "platform.h"
30 #ifdef USE_OSD
32 #include "config/config.h"
33 #include "config/feature.h"
35 #include "common/maths.h"
36 #include "common/printf.h"
38 #include "drivers/display.h"
39 #include "drivers/osd_symbols.h"
40 #include "drivers/time.h"
42 #include "fc/core.h"
43 #include "fc/rc.h"
44 #include "fc/rc_modes.h"
45 #include "fc/runtime_config.h"
47 #include "flight/failsafe.h"
48 #include "flight/gps_rescue.h"
49 #include "flight/imu.h"
50 #include "flight/mixer.h"
51 #include "flight/pid.h"
53 #include "io/beeper.h"
55 #include "osd/osd.h"
56 #include "osd/osd_elements.h"
57 #include "osd/osd_warnings.h"
59 #include "rx/rx.h"
61 #include "sensors/acceleration.h"
62 #include "sensors/adcinternal.h"
63 #include "sensors/battery.h"
64 #include "sensors/sensors.h"
66 void renderOsdWarning(char *warningText, bool *blinking, uint8_t *displayAttr)
68 const batteryState_e batteryState = getBatteryState();
69 const timeUs_t currentTimeUs = micros();
71 static timeUs_t armingDisabledUpdateTimeUs;
72 static unsigned armingDisabledDisplayIndex;
74 warningText[0] = '\0';
75 *displayAttr = DISPLAYPORT_ATTR_NONE;
76 *blinking = false;
78 // Cycle through the arming disabled reasons
79 if (osdWarnGetState(OSD_WARNING_ARMING_DISABLE)) {
80 if (IS_RC_MODE_ACTIVE(BOXARM) && isArmingDisabled()) {
81 const armingDisableFlags_e armSwitchOnlyFlag = 1 << (ARMING_DISABLE_FLAGS_COUNT - 1);
82 armingDisableFlags_e flags = getArmingDisableFlags();
84 // Remove the ARMSWITCH flag unless it's the only one
85 if ((flags & armSwitchOnlyFlag) && (flags != armSwitchOnlyFlag)) {
86 flags -= armSwitchOnlyFlag;
89 // Rotate to the next arming disabled reason after a 0.5 second time delay
90 // or if the current flag is no longer set
91 if ((currentTimeUs - armingDisabledUpdateTimeUs > 5e5) || !(flags & (1 << armingDisabledDisplayIndex))) {
92 if (armingDisabledUpdateTimeUs == 0) {
93 armingDisabledDisplayIndex = ARMING_DISABLE_FLAGS_COUNT - 1;
95 armingDisabledUpdateTimeUs = currentTimeUs;
97 do {
98 if (++armingDisabledDisplayIndex >= ARMING_DISABLE_FLAGS_COUNT) {
99 armingDisabledDisplayIndex = 0;
101 } while (!(flags & (1 << armingDisabledDisplayIndex)));
104 tfp_sprintf(warningText, "%s", armingDisableFlagNames[armingDisabledDisplayIndex]);
105 *displayAttr = DISPLAYPORT_ATTR_WARNING;
106 return;
107 } else {
108 armingDisabledUpdateTimeUs = 0;
112 #ifdef USE_DSHOT
113 if (isTryingToArm() && !ARMING_FLAG(ARMED)) {
114 int armingDelayTime = (getLastDshotBeaconCommandTimeUs() + DSHOT_BEACON_GUARD_DELAY_US - currentTimeUs) / 1e5;
115 if (armingDelayTime < 0) {
116 armingDelayTime = 0;
118 if (armingDelayTime >= (DSHOT_BEACON_GUARD_DELAY_US / 1e5 - 5)) {
119 tfp_sprintf(warningText, " BEACON ON"); // Display this message for the first 0.5 seconds
120 } else {
121 tfp_sprintf(warningText, "ARM IN %d.%d", armingDelayTime / 10, armingDelayTime % 10);
123 *displayAttr = DISPLAYPORT_ATTR_INFO;
124 return;
126 #endif // USE_DSHOT
127 if (osdWarnGetState(OSD_WARNING_FAIL_SAFE) && failsafeIsActive()) {
128 tfp_sprintf(warningText, "FAIL SAFE");
129 *displayAttr = DISPLAYPORT_ATTR_CRITICAL;
130 *blinking = true;;
131 return;
134 // Warn when in flip over after crash mode
135 if (osdWarnGetState(OSD_WARNING_CRASH_FLIP) && isFlipOverAfterCrashActive()) {
136 tfp_sprintf(warningText, "CRASH FLIP");
137 *displayAttr = DISPLAYPORT_ATTR_INFO;
138 return;
141 #ifdef USE_LAUNCH_CONTROL
142 // Warn when in launch control mode
143 if (osdWarnGetState(OSD_WARNING_LAUNCH_CONTROL) && isLaunchControlActive()) {
144 #ifdef USE_ACC
145 if (sensors(SENSOR_ACC)) {
146 const int pitchAngle = constrain((attitude.raw[FD_PITCH] - accelerometerConfig()->accelerometerTrims.raw[FD_PITCH]) / 10, -90, 90);
147 tfp_sprintf(warningText, "LAUNCH %d", pitchAngle);
148 } else
149 #endif // USE_ACC
151 tfp_sprintf(warningText, "LAUNCH");
154 // Blink the message if the throttle is within 10% of the launch setting
155 if ( calculateThrottlePercent() >= MAX(currentPidProfile->launchControlThrottlePercent - 10, 0)) {
156 *blinking = true;;
159 *displayAttr = DISPLAYPORT_ATTR_INFO;
160 return;
162 #endif // USE_LAUNCH_CONTROL
164 // RSSI
165 if (osdWarnGetState(OSD_WARNING_RSSI) && (getRssiPercent() < osdConfig()->rssi_alarm)) {
166 tfp_sprintf(warningText, "RSSI LOW");
167 *displayAttr = DISPLAYPORT_ATTR_WARNING;
168 *blinking = true;;
169 return;
171 #ifdef USE_RX_RSSI_DBM
172 // rssi dbm
173 if (osdWarnGetState(OSD_WARNING_RSSI_DBM) && (getRssiDbm() < osdConfig()->rssi_dbm_alarm)) {
174 tfp_sprintf(warningText, "RSSI DBM");
175 *displayAttr = DISPLAYPORT_ATTR_WARNING;
176 *blinking = true;;
177 return;
179 #endif // USE_RX_RSSI_DBM
181 #ifdef USE_RX_LINK_QUALITY_INFO
182 // Link Quality
183 if (osdWarnGetState(OSD_WARNING_LINK_QUALITY) && (rxGetLinkQualityPercent() < osdConfig()->link_quality_alarm)) {
184 tfp_sprintf(warningText, "LINK QUALITY");
185 *displayAttr = DISPLAYPORT_ATTR_WARNING;
186 *blinking = true;;
187 return;
189 #endif // USE_RX_LINK_QUALITY_INFO
191 if (osdWarnGetState(OSD_WARNING_BATTERY_CRITICAL) && batteryState == BATTERY_CRITICAL) {
192 tfp_sprintf(warningText, " LAND NOW");
193 *displayAttr = DISPLAYPORT_ATTR_CRITICAL;
194 *blinking = true;;
195 return;
198 #ifdef USE_GPS_RESCUE
199 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_UNAVAILABLE) &&
200 ARMING_FLAG(ARMED) &&
201 gpsRescueIsConfigured() &&
202 !gpsRescueIsDisabled() &&
203 !gpsRescueIsAvailable()) {
204 tfp_sprintf(warningText, "RESCUE N/A");
205 *displayAttr = DISPLAYPORT_ATTR_WARNING;
206 *blinking = true;;
207 return;
210 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_DISABLED) &&
211 ARMING_FLAG(ARMED) &&
212 gpsRescueIsConfigured() &&
213 gpsRescueIsDisabled()) {
215 statistic_t *stats = osdGetStats();
216 if (cmpTimeUs(stats->armed_time, OSD_GPS_RESCUE_DISABLED_WARNING_DURATION_US) < 0) {
217 tfp_sprintf(warningText, "RESCUE OFF");
218 *displayAttr = DISPLAYPORT_ATTR_WARNING;
219 *blinking = true;;
220 return;
224 #endif // USE_GPS_RESCUE
226 // Show warning if in HEADFREE flight mode
227 if (FLIGHT_MODE(HEADFREE_MODE)) {
228 tfp_sprintf(warningText, "HEADFREE");
229 *displayAttr = DISPLAYPORT_ATTR_WARNING;
230 *blinking = true;;
231 return;
234 #ifdef USE_ADC_INTERNAL
235 const int16_t coreTemperature = getCoreTemperatureCelsius();
236 if (osdWarnGetState(OSD_WARNING_CORE_TEMPERATURE) && coreTemperature >= osdConfig()->core_temp_alarm) {
237 tfp_sprintf(warningText, "CORE %c: %3d%c", SYM_TEMPERATURE, osdConvertTemperatureToSelectedUnit(coreTemperature), osdGetTemperatureSymbolForSelectedUnit());
238 *displayAttr = DISPLAYPORT_ATTR_WARNING;
239 *blinking = true;;
240 return;
242 #endif // USE_ADC_INTERNAL
244 #ifdef USE_ESC_SENSOR
245 // Show warning if we lose motor output, the ESC is overheating or excessive current draw
246 if (featureIsEnabled(FEATURE_ESC_SENSOR) && osdWarnGetState(OSD_WARNING_ESC_FAIL)) {
247 char escWarningMsg[OSD_FORMAT_MESSAGE_BUFFER_SIZE];
248 unsigned pos = 0;
250 const char *title = "ESC";
252 // center justify message
253 while (pos < (OSD_WARNINGS_MAX_SIZE - (strlen(title) + getMotorCount())) / 2) {
254 escWarningMsg[pos++] = ' ';
257 strcpy(escWarningMsg + pos, title);
258 pos += strlen(title);
260 unsigned i = 0;
261 unsigned escWarningCount = 0;
262 while (i < getMotorCount() && pos < OSD_FORMAT_MESSAGE_BUFFER_SIZE - 1) {
263 escSensorData_t *escData = getEscSensorData(i);
264 const char motorNumber = '1' + i;
265 // if everything is OK just display motor number else R, T or C
266 char warnFlag = motorNumber;
267 if (ARMING_FLAG(ARMED) && osdConfig()->esc_rpm_alarm != ESC_RPM_ALARM_OFF && calcEscRpm(escData->rpm) <= osdConfig()->esc_rpm_alarm) {
268 warnFlag = 'R';
270 if (osdConfig()->esc_temp_alarm != ESC_TEMP_ALARM_OFF && escData->temperature >= osdConfig()->esc_temp_alarm) {
271 warnFlag = 'T';
273 if (ARMING_FLAG(ARMED) && osdConfig()->esc_current_alarm != ESC_CURRENT_ALARM_OFF && escData->current >= osdConfig()->esc_current_alarm) {
274 warnFlag = 'C';
277 escWarningMsg[pos++] = warnFlag;
279 if (warnFlag != motorNumber) {
280 escWarningCount++;
283 i++;
286 escWarningMsg[pos] = '\0';
288 if (escWarningCount > 0) {
289 tfp_sprintf(warningText, "%s", escWarningMsg);
290 *displayAttr = DISPLAYPORT_ATTR_WARNING;
291 *blinking = true;;
292 return;
295 #endif // USE_ESC_SENSOR
297 if (osdWarnGetState(OSD_WARNING_BATTERY_WARNING) && batteryState == BATTERY_WARNING) {
298 tfp_sprintf(warningText, "LOW BATTERY");
299 *displayAttr = DISPLAYPORT_ATTR_WARNING;
300 *blinking = true;;
301 return;
304 #ifdef USE_RC_SMOOTHING_FILTER
305 // Show warning if rc smoothing hasn't initialized the filters
306 if (osdWarnGetState(OSD_WARNING_RC_SMOOTHING) && ARMING_FLAG(ARMED) && !rcSmoothingInitializationComplete()) {
307 tfp_sprintf(warningText, "RCSMOOTHING");
308 *displayAttr = DISPLAYPORT_ATTR_WARNING;
309 *blinking = true;;
310 return;
312 #endif // USE_RC_SMOOTHING_FILTER
314 // Show warning if mah consumed is over the configured limit
315 if (osdWarnGetState(OSD_WARNING_OVER_CAP) && ARMING_FLAG(ARMED) && osdConfig()->cap_alarm > 0 && getMAhDrawn() >= osdConfig()->cap_alarm) {
316 tfp_sprintf(warningText, "OVER CAP");
317 *displayAttr = DISPLAYPORT_ATTR_WARNING;
318 *blinking = true;;
319 return;
322 // Show warning if battery is not fresh
323 if (osdWarnGetState(OSD_WARNING_BATTERY_NOT_FULL) && !(ARMING_FLAG(ARMED) || ARMING_FLAG(WAS_EVER_ARMED)) && (getBatteryState() == BATTERY_OK)
324 && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage) {
325 tfp_sprintf(warningText, "BATT < FULL");
326 *displayAttr = DISPLAYPORT_ATTR_INFO;
327 return;
330 // Visual beeper
331 if (osdWarnGetState(OSD_WARNING_VISUAL_BEEPER) && osdGetVisualBeeperState()) {
332 tfp_sprintf(warningText, " * * * *");
333 *displayAttr = DISPLAYPORT_ATTR_INFO;
334 return;
339 #endif // USE_OSD