Use cpu_late_10ths_percent_limit to set limit on % of late tasks in 10th of a % ...
[betaflight.git] / src / main / osd / osd_warnings.c
blob4b5e57ccb28285099a157d427b89608a5e1cc481
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"
41 #include "drivers/dshot.h"
43 #include "fc/core.h"
44 #include "fc/rc.h"
45 #include "fc/rc_modes.h"
46 #include "fc/runtime_config.h"
48 #include "flight/failsafe.h"
49 #include "flight/gps_rescue.h"
50 #include "flight/imu.h"
51 #include "flight/mixer.h"
52 #include "flight/pid.h"
54 #include "io/beeper.h"
56 #include "osd/osd.h"
57 #include "osd/osd_elements.h"
58 #include "osd/osd_warnings.h"
60 #include "rx/rx.h"
62 #include "sensors/acceleration.h"
63 #include "sensors/adcinternal.h"
64 #include "sensors/battery.h"
65 #include "sensors/sensors.h"
67 const char CRASH_FLIP_WARNING[] = "> CRASH FLIP <";
69 void renderOsdWarning(char *warningText, bool *blinking, uint8_t *displayAttr)
71 const batteryState_e batteryState = getBatteryState();
72 const timeUs_t currentTimeUs = micros();
74 static timeUs_t armingDisabledUpdateTimeUs;
75 static unsigned armingDisabledDisplayIndex;
77 warningText[0] = '\0';
78 *displayAttr = DISPLAYPORT_SEVERITY_NORMAL;
79 *blinking = false;
81 // Cycle through the arming disabled reasons
82 if (osdWarnGetState(OSD_WARNING_ARMING_DISABLE)) {
83 if (IS_RC_MODE_ACTIVE(BOXARM) && isArmingDisabled()) {
84 const armingDisableFlags_e armSwitchOnlyFlag = 1 << (ARMING_DISABLE_FLAGS_COUNT - 1);
85 armingDisableFlags_e flags = getArmingDisableFlags();
87 // Remove the ARMSWITCH flag unless it's the only one
88 if ((flags & armSwitchOnlyFlag) && (flags != armSwitchOnlyFlag)) {
89 flags -= armSwitchOnlyFlag;
92 // Rotate to the next arming disabled reason after a 0.5 second time delay
93 // or if the current flag is no longer set
94 if ((currentTimeUs - armingDisabledUpdateTimeUs > 5e5) || !(flags & (1 << armingDisabledDisplayIndex))) {
95 if (armingDisabledUpdateTimeUs == 0) {
96 armingDisabledDisplayIndex = ARMING_DISABLE_FLAGS_COUNT - 1;
98 armingDisabledUpdateTimeUs = currentTimeUs;
100 do {
101 if (++armingDisabledDisplayIndex >= ARMING_DISABLE_FLAGS_COUNT) {
102 armingDisabledDisplayIndex = 0;
104 } while (!(flags & (1 << armingDisabledDisplayIndex)));
107 tfp_sprintf(warningText, "%s", armingDisableFlagNames[armingDisabledDisplayIndex]);
108 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
109 return;
110 } else {
111 armingDisabledUpdateTimeUs = 0;
115 #ifdef USE_DSHOT
116 if (isTryingToArm() && !ARMING_FLAG(ARMED)) {
117 int armingDelayTime = (getLastDshotBeaconCommandTimeUs() + DSHOT_BEACON_GUARD_DELAY_US - currentTimeUs) / 1e5;
118 if (armingDelayTime < 0) {
119 armingDelayTime = 0;
121 if (armingDelayTime >= (DSHOT_BEACON_GUARD_DELAY_US / 1e5 - 5)) {
122 tfp_sprintf(warningText, " BEACON ON"); // Display this message for the first 0.5 seconds
123 } else {
124 tfp_sprintf(warningText, "ARM IN %d.%d", armingDelayTime / 10, armingDelayTime % 10);
126 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
127 return;
129 #endif // USE_DSHOT
130 if (osdWarnGetState(OSD_WARNING_FAIL_SAFE) && failsafeIsActive()) {
131 tfp_sprintf(warningText, "FAIL SAFE");
132 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
133 *blinking = true;
134 return;
137 // Warn when in flip over after crash mode
138 if (osdWarnGetState(OSD_WARNING_CRASH_FLIP) && IS_RC_MODE_ACTIVE(BOXFLIPOVERAFTERCRASH)) {
139 if (isFlipOverAfterCrashActive()) { // if was armed in crash flip mode
140 tfp_sprintf(warningText, CRASH_FLIP_WARNING);
141 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
142 return;
143 } else if (!ARMING_FLAG(ARMED)) { // if disarmed, but crash flip mode is activated
144 tfp_sprintf(warningText, "CRASH FLIP SWITCH");
145 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
146 return;
150 #ifdef USE_LAUNCH_CONTROL
151 // Warn when in launch control mode
152 if (osdWarnGetState(OSD_WARNING_LAUNCH_CONTROL) && isLaunchControlActive()) {
153 #ifdef USE_ACC
154 if (sensors(SENSOR_ACC)) {
155 const int pitchAngle = constrain((attitude.raw[FD_PITCH] - accelerometerConfig()->accelerometerTrims.raw[FD_PITCH]) / 10, -90, 90);
156 tfp_sprintf(warningText, "LAUNCH %d", pitchAngle);
157 } else
158 #endif // USE_ACC
160 tfp_sprintf(warningText, "LAUNCH");
163 // Blink the message if the throttle is within 10% of the launch setting
164 if ( calculateThrottlePercent() >= MAX(currentPidProfile->launchControlThrottlePercent - 10, 0)) {
165 *blinking = true;
168 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
169 return;
171 #endif // USE_LAUNCH_CONTROL
173 // RSSI
174 if (osdWarnGetState(OSD_WARNING_RSSI) && (getRssiPercent() < osdConfig()->rssi_alarm)) {
175 tfp_sprintf(warningText, "RSSI LOW");
176 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
177 *blinking = true;
178 return;
180 #ifdef USE_RX_RSSI_DBM
181 // rssi dbm
182 if (osdWarnGetState(OSD_WARNING_RSSI_DBM) && (getRssiDbm() < osdConfig()->rssi_dbm_alarm)) {
183 tfp_sprintf(warningText, "RSSI DBM");
184 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
185 *blinking = true;
186 return;
188 #endif // USE_RX_RSSI_DBM
189 #ifdef USE_RX_RSNR
190 // rsnr
191 if (osdWarnGetState(OSD_WARNING_RSNR) && (getRsnr() < osdConfig()->rsnr_alarm)) {
192 tfp_sprintf(warningText, "RSNR LOW");
193 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
194 *blinking = true;
195 return;
197 #endif // USE_RX_RSNR
199 #ifdef USE_RX_LINK_QUALITY_INFO
200 // Link Quality
201 if (osdWarnGetState(OSD_WARNING_LINK_QUALITY) && (rxGetLinkQualityPercent() < osdConfig()->link_quality_alarm)) {
202 tfp_sprintf(warningText, "LINK QUALITY");
203 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
204 *blinking = true;
205 return;
207 #endif // USE_RX_LINK_QUALITY_INFO
209 if (osdWarnGetState(OSD_WARNING_BATTERY_CRITICAL) && batteryState == BATTERY_CRITICAL) {
210 tfp_sprintf(warningText, " LAND NOW");
211 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
212 *blinking = true;
213 return;
216 if (osdWarnGetState(OSD_WARNING_LOAD) && (getArmingDisableFlags() & ARMING_DISABLED_LOAD)) {
217 tfp_sprintf(warningText, "CPU OVERLOAD");
218 *displayAttr = DISPLAYPORT_SEVERITY_CRITICAL;
219 *blinking = true;
220 return;
223 #ifdef USE_GPS_RESCUE
224 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_UNAVAILABLE) &&
225 ARMING_FLAG(ARMED) &&
226 gpsRescueIsConfigured() &&
227 !gpsRescueIsDisabled() &&
228 !gpsRescueIsAvailable()) {
229 tfp_sprintf(warningText, "RESCUE N/A");
230 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
231 *blinking = true;
232 return;
235 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_DISABLED) &&
236 ARMING_FLAG(ARMED) &&
237 gpsRescueIsConfigured() &&
238 gpsRescueIsDisabled()) {
240 statistic_t *stats = osdGetStats();
241 if (cmpTimeUs(stats->armed_time, OSD_GPS_RESCUE_DISABLED_WARNING_DURATION_US) < 0) {
242 tfp_sprintf(warningText, "RESCUE OFF");
243 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
244 *blinking = true;
245 return;
249 #endif // USE_GPS_RESCUE
251 // Show warning if in HEADFREE flight mode
252 if (FLIGHT_MODE(HEADFREE_MODE)) {
253 tfp_sprintf(warningText, "HEADFREE");
254 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
255 *blinking = true;
256 return;
259 #ifdef USE_ADC_INTERNAL
260 const int16_t coreTemperature = getCoreTemperatureCelsius();
261 if (osdWarnGetState(OSD_WARNING_CORE_TEMPERATURE) && coreTemperature >= osdConfig()->core_temp_alarm) {
262 tfp_sprintf(warningText, "CORE %c: %3d%c", SYM_TEMPERATURE, osdConvertTemperatureToSelectedUnit(coreTemperature), osdGetTemperatureSymbolForSelectedUnit());
263 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
264 *blinking = true;
265 return;
267 #endif // USE_ADC_INTERNAL
269 #ifdef USE_ESC_SENSOR
270 // Show warning if we lose motor output, the ESC is overheating or excessive current draw
271 if (featureIsEnabled(FEATURE_ESC_SENSOR) && osdWarnGetState(OSD_WARNING_ESC_FAIL)) {
272 char escWarningMsg[OSD_FORMAT_MESSAGE_BUFFER_SIZE];
273 unsigned pos = 0;
275 const char *title = "ESC";
277 // center justify message
278 while (pos < (OSD_WARNINGS_MAX_SIZE - (strlen(title) + getMotorCount())) / 2) {
279 escWarningMsg[pos++] = ' ';
282 strcpy(escWarningMsg + pos, title);
283 pos += strlen(title);
285 unsigned i = 0;
286 unsigned escWarningCount = 0;
287 while (i < getMotorCount() && pos < OSD_FORMAT_MESSAGE_BUFFER_SIZE - 1) {
288 escSensorData_t *escData = getEscSensorData(i);
289 const char motorNumber = '1' + i;
290 // if everything is OK just display motor number else R, T or C
291 char warnFlag = motorNumber;
292 if (ARMING_FLAG(ARMED) && osdConfig()->esc_rpm_alarm != ESC_RPM_ALARM_OFF && (uint32_t)erpmToRpm(escData->rpm) <= (uint32_t)osdConfig()->esc_rpm_alarm) {
293 warnFlag = 'R';
295 if (osdConfig()->esc_temp_alarm != ESC_TEMP_ALARM_OFF && escData->temperature >= osdConfig()->esc_temp_alarm) {
296 warnFlag = 'T';
298 if (ARMING_FLAG(ARMED) && osdConfig()->esc_current_alarm != ESC_CURRENT_ALARM_OFF && escData->current >= osdConfig()->esc_current_alarm) {
299 warnFlag = 'C';
302 escWarningMsg[pos++] = warnFlag;
304 if (warnFlag != motorNumber) {
305 escWarningCount++;
308 i++;
311 escWarningMsg[pos] = '\0';
313 if (escWarningCount > 0) {
314 tfp_sprintf(warningText, "%s", escWarningMsg);
315 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
316 *blinking = true;
317 return;
320 #endif // USE_ESC_SENSOR
322 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
323 // Show esc error
324 if (osdWarnGetState(OSD_WARNING_ESC_FAIL)) {
325 uint32_t dshotEscErrorLengthMotorBegin;
326 uint32_t dshotEscErrorLength = 0;
328 // Write 'ESC'
329 warningText[dshotEscErrorLength++] = 'E';
330 warningText[dshotEscErrorLength++] = 'S';
331 warningText[dshotEscErrorLength++] = 'C';
333 for (uint8_t k = 0; k < getMotorCount(); k++) {
334 // Skip if no extended telemetry at all
335 if ((dshotTelemetryState.motorState[k].telemetryTypes & DSHOT_EXTENDED_TELEMETRY_MASK) == 0) {
336 continue;
339 // Remember text index before writing warnings
340 dshotEscErrorLengthMotorBegin = dshotEscErrorLength;
342 // Write ESC nr
343 warningText[dshotEscErrorLength++] = ' ';
344 warningText[dshotEscErrorLength++] = '0' + k + 1;
346 // Add esc warnings
347 if (ARMING_FLAG(ARMED) && osdConfig()->esc_rpm_alarm != ESC_RPM_ALARM_OFF
348 && isDshotMotorTelemetryActive(k)
349 && (dshotTelemetryState.motorState[k].telemetryData[DSHOT_TELEMETRY_TYPE_eRPM] * 100 * 2 / motorConfig()->motorPoleCount) <= osdConfig()->esc_rpm_alarm) {
350 warningText[dshotEscErrorLength++] = 'R';
352 if (osdConfig()->esc_temp_alarm != ESC_TEMP_ALARM_OFF
353 && (dshotTelemetryState.motorState[k].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) != 0
354 && dshotTelemetryState.motorState[k].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE] >= osdConfig()->esc_temp_alarm) {
355 warningText[dshotEscErrorLength++] = 'T';
357 if (ARMING_FLAG(ARMED) && osdConfig()->esc_current_alarm != ESC_CURRENT_ALARM_OFF
358 && (dshotTelemetryState.motorState[k].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) != 0
359 && dshotTelemetryState.motorState[k].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT] >= osdConfig()->esc_current_alarm) {
360 warningText[dshotEscErrorLength++] = 'C';
363 // If no esc warning data undo esc nr (esc telemetry data types depends on the esc hw/sw)
364 if (dshotEscErrorLengthMotorBegin + 2 == dshotEscErrorLength)
365 dshotEscErrorLength = dshotEscErrorLengthMotorBegin;
368 // If warning exists then notify, otherwise clear warning message
369 if (dshotEscErrorLength > 3) {
370 warningText[dshotEscErrorLength] = 0; // End string
371 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
372 *blinking = true;
373 return;
374 } else {
375 warningText[0] = 0;
378 #endif
380 if (osdWarnGetState(OSD_WARNING_BATTERY_WARNING) && batteryState == BATTERY_WARNING) {
381 tfp_sprintf(warningText, "LOW BATTERY");
382 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
383 *blinking = true;
384 return;
387 #ifdef USE_RC_SMOOTHING_FILTER
388 // Show warning if rc smoothing hasn't initialized the filters
389 if (osdWarnGetState(OSD_WARNING_RC_SMOOTHING) && ARMING_FLAG(ARMED) && !rcSmoothingInitializationComplete() && rxConfig()->rc_smoothing_mode) {
390 tfp_sprintf(warningText, "RCSMOOTHING");
391 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
392 *blinking = true;
393 return;
395 #endif // USE_RC_SMOOTHING_FILTER
397 // Show warning if mah consumed is over the configured limit
398 if (osdWarnGetState(OSD_WARNING_OVER_CAP) && ARMING_FLAG(ARMED) && osdConfig()->cap_alarm > 0 && getMAhDrawn() >= osdConfig()->cap_alarm) {
399 tfp_sprintf(warningText, "OVER CAP");
400 *displayAttr = DISPLAYPORT_SEVERITY_WARNING;
401 *blinking = true;
402 return;
405 #ifdef USE_BATTERY_CONTINUE
406 // Show warning if battery is not fresh and battery continue is active
407 if (hasUsedMAh()) {
408 tfp_sprintf(warningText, "BATTERY CONTINUE");
409 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
410 return;
412 #endif // USE_BATTERY_CONTINUE
414 // Show warning if battery is not fresh
415 if (osdWarnGetState(OSD_WARNING_BATTERY_NOT_FULL) && !(ARMING_FLAG(ARMED) || ARMING_FLAG(WAS_EVER_ARMED)) && (getBatteryState() == BATTERY_OK)
416 && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage) {
417 tfp_sprintf(warningText, "BATT < FULL");
418 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
419 return;
422 // Visual beeper
423 if (osdWarnGetState(OSD_WARNING_VISUAL_BEEPER) && osdGetVisualBeeperState()) {
424 tfp_sprintf(warningText, " * * * *");
425 *displayAttr = DISPLAYPORT_SEVERITY_INFO;
426 osdSetVisualBeeperState(false);
427 return;
432 #endif // USE_OSD