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)
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/>.
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"
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"
57 #include "osd/osd_elements.h"
58 #include "osd/osd_warnings.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_ATTR_NONE
;
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
;
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_ATTR_WARNING
;
111 armingDisabledUpdateTimeUs
= 0;
116 if (isTryingToArm() && !ARMING_FLAG(ARMED
)) {
117 int armingDelayTime
= (getLastDshotBeaconCommandTimeUs() + DSHOT_BEACON_GUARD_DELAY_US
- currentTimeUs
) / 1e5
;
118 if (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
124 tfp_sprintf(warningText
, "ARM IN %d.%d", armingDelayTime
/ 10, armingDelayTime
% 10);
126 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
130 if (osdWarnGetState(OSD_WARNING_FAIL_SAFE
) && failsafeIsActive()) {
131 tfp_sprintf(warningText
, "FAIL SAFE");
132 *displayAttr
= DISPLAYPORT_ATTR_CRITICAL
;
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_ATTR_INFO
;
143 } else if (!ARMING_FLAG(ARMED
)) { // if disarmed, but crash flip mode is activated
144 tfp_sprintf(warningText
, "CRASH FLIP SWITCH");
145 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
150 #ifdef USE_LAUNCH_CONTROL
151 // Warn when in launch control mode
152 if (osdWarnGetState(OSD_WARNING_LAUNCH_CONTROL
) && isLaunchControlActive()) {
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
);
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)) {
168 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
171 #endif // USE_LAUNCH_CONTROL
174 if (osdWarnGetState(OSD_WARNING_RSSI
) && (getRssiPercent() < osdConfig()->rssi_alarm
)) {
175 tfp_sprintf(warningText
, "RSSI LOW");
176 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
180 #ifdef USE_RX_RSSI_DBM
182 if (osdWarnGetState(OSD_WARNING_RSSI_DBM
) && (getRssiDbm() < osdConfig()->rssi_dbm_alarm
)) {
183 tfp_sprintf(warningText
, "RSSI DBM");
184 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
188 #endif // USE_RX_RSSI_DBM
190 #ifdef USE_RX_LINK_QUALITY_INFO
192 if (osdWarnGetState(OSD_WARNING_LINK_QUALITY
) && (rxGetLinkQualityPercent() < osdConfig()->link_quality_alarm
)) {
193 tfp_sprintf(warningText
, "LINK QUALITY");
194 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
198 #endif // USE_RX_LINK_QUALITY_INFO
200 if (osdWarnGetState(OSD_WARNING_BATTERY_CRITICAL
) && batteryState
== BATTERY_CRITICAL
) {
201 tfp_sprintf(warningText
, " LAND NOW");
202 *displayAttr
= DISPLAYPORT_ATTR_CRITICAL
;
207 #ifdef USE_GPS_RESCUE
208 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_UNAVAILABLE
) &&
209 ARMING_FLAG(ARMED
) &&
210 gpsRescueIsConfigured() &&
211 !gpsRescueIsDisabled() &&
212 !gpsRescueIsAvailable()) {
213 tfp_sprintf(warningText
, "RESCUE N/A");
214 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
219 if (osdWarnGetState(OSD_WARNING_GPS_RESCUE_DISABLED
) &&
220 ARMING_FLAG(ARMED
) &&
221 gpsRescueIsConfigured() &&
222 gpsRescueIsDisabled()) {
224 statistic_t
*stats
= osdGetStats();
225 if (cmpTimeUs(stats
->armed_time
, OSD_GPS_RESCUE_DISABLED_WARNING_DURATION_US
) < 0) {
226 tfp_sprintf(warningText
, "RESCUE OFF");
227 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
233 #endif // USE_GPS_RESCUE
235 // Show warning if in HEADFREE flight mode
236 if (FLIGHT_MODE(HEADFREE_MODE
)) {
237 tfp_sprintf(warningText
, "HEADFREE");
238 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
243 #ifdef USE_ADC_INTERNAL
244 const int16_t coreTemperature
= getCoreTemperatureCelsius();
245 if (osdWarnGetState(OSD_WARNING_CORE_TEMPERATURE
) && coreTemperature
>= osdConfig()->core_temp_alarm
) {
246 tfp_sprintf(warningText
, "CORE %c: %3d%c", SYM_TEMPERATURE
, osdConvertTemperatureToSelectedUnit(coreTemperature
), osdGetTemperatureSymbolForSelectedUnit());
247 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
251 #endif // USE_ADC_INTERNAL
253 #ifdef USE_ESC_SENSOR
254 // Show warning if we lose motor output, the ESC is overheating or excessive current draw
255 if (featureIsEnabled(FEATURE_ESC_SENSOR
) && osdWarnGetState(OSD_WARNING_ESC_FAIL
)) {
256 char escWarningMsg
[OSD_FORMAT_MESSAGE_BUFFER_SIZE
];
259 const char *title
= "ESC";
261 // center justify message
262 while (pos
< (OSD_WARNINGS_MAX_SIZE
- (strlen(title
) + getMotorCount())) / 2) {
263 escWarningMsg
[pos
++] = ' ';
266 strcpy(escWarningMsg
+ pos
, title
);
267 pos
+= strlen(title
);
270 unsigned escWarningCount
= 0;
271 while (i
< getMotorCount() && pos
< OSD_FORMAT_MESSAGE_BUFFER_SIZE
- 1) {
272 escSensorData_t
*escData
= getEscSensorData(i
);
273 const char motorNumber
= '1' + i
;
274 // if everything is OK just display motor number else R, T or C
275 char warnFlag
= motorNumber
;
276 if (ARMING_FLAG(ARMED
) && osdConfig()->esc_rpm_alarm
!= ESC_RPM_ALARM_OFF
&& calcEscRpm(escData
->rpm
) <= osdConfig()->esc_rpm_alarm
) {
279 if (osdConfig()->esc_temp_alarm
!= ESC_TEMP_ALARM_OFF
&& escData
->temperature
>= osdConfig()->esc_temp_alarm
) {
282 if (ARMING_FLAG(ARMED
) && osdConfig()->esc_current_alarm
!= ESC_CURRENT_ALARM_OFF
&& escData
->current
>= osdConfig()->esc_current_alarm
) {
286 escWarningMsg
[pos
++] = warnFlag
;
288 if (warnFlag
!= motorNumber
) {
295 escWarningMsg
[pos
] = '\0';
297 if (escWarningCount
> 0) {
298 tfp_sprintf(warningText
, "%s", escWarningMsg
);
299 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
304 #endif // USE_ESC_SENSOR
306 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
308 if (osdWarnGetState(OSD_WARNING_ESC_FAIL
)) {
309 uint32_t dshotEscErrorLengthMotorBegin
;
310 uint32_t dshotEscErrorLength
= 0;
313 warningText
[dshotEscErrorLength
++] = 'E';
314 warningText
[dshotEscErrorLength
++] = 'S';
315 warningText
[dshotEscErrorLength
++] = 'C';
317 for (uint8_t k
= 0; k
< getMotorCount(); k
++) {
318 // Skip if no extended telemetry at all
319 if ((dshotTelemetryState
.motorState
[k
].telemetryTypes
& DSHOT_EXTENDED_TELEMETRY_MASK
) == 0) {
323 // Remember text index before writing warnings
324 dshotEscErrorLengthMotorBegin
= dshotEscErrorLength
;
327 warningText
[dshotEscErrorLength
++] = ' ';
328 warningText
[dshotEscErrorLength
++] = '0' + k
+ 1;
331 if (ARMING_FLAG(ARMED
) && osdConfig()->esc_rpm_alarm
!= ESC_RPM_ALARM_OFF
332 && (dshotTelemetryState
.motorState
[k
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_eRPM
)) != 0
333 && (dshotTelemetryState
.motorState
[k
].telemetryData
[DSHOT_TELEMETRY_TYPE_eRPM
] * 100 * 2 / motorConfig()->motorPoleCount
) <= osdConfig()->esc_rpm_alarm
) {
334 warningText
[dshotEscErrorLength
++] = 'R';
336 if (osdConfig()->esc_temp_alarm
!= ESC_TEMP_ALARM_OFF
337 && (dshotTelemetryState
.motorState
[k
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE
)) != 0
338 && dshotTelemetryState
.motorState
[k
].telemetryData
[DSHOT_TELEMETRY_TYPE_TEMPERATURE
] >= osdConfig()->esc_temp_alarm
) {
339 warningText
[dshotEscErrorLength
++] = 'T';
341 if (ARMING_FLAG(ARMED
) && osdConfig()->esc_current_alarm
!= ESC_CURRENT_ALARM_OFF
342 && (dshotTelemetryState
.motorState
[k
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_CURRENT
)) != 0
343 && dshotTelemetryState
.motorState
[k
].telemetryData
[DSHOT_TELEMETRY_TYPE_CURRENT
] >= osdConfig()->esc_current_alarm
) {
344 warningText
[dshotEscErrorLength
++] = 'C';
347 // If no esc warning data undo esc nr (esc telemetry data types depends on the esc hw/sw)
348 if (dshotEscErrorLengthMotorBegin
+ 2 == dshotEscErrorLength
)
349 dshotEscErrorLength
= dshotEscErrorLengthMotorBegin
;
352 // If warning exists then notify, otherwise clear warning message
353 if (dshotEscErrorLength
> 3) {
354 warningText
[dshotEscErrorLength
] = 0; // End string
355 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
364 if (osdWarnGetState(OSD_WARNING_BATTERY_WARNING
) && batteryState
== BATTERY_WARNING
) {
365 tfp_sprintf(warningText
, "LOW BATTERY");
366 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
371 #ifdef USE_RC_SMOOTHING_FILTER
372 // Show warning if rc smoothing hasn't initialized the filters
373 if (osdWarnGetState(OSD_WARNING_RC_SMOOTHING
) && ARMING_FLAG(ARMED
) && !rcSmoothingInitializationComplete()) {
374 tfp_sprintf(warningText
, "RCSMOOTHING");
375 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
379 #endif // USE_RC_SMOOTHING_FILTER
381 // Show warning if mah consumed is over the configured limit
382 if (osdWarnGetState(OSD_WARNING_OVER_CAP
) && ARMING_FLAG(ARMED
) && osdConfig()->cap_alarm
> 0 && getMAhDrawn() >= osdConfig()->cap_alarm
) {
383 tfp_sprintf(warningText
, "OVER CAP");
384 *displayAttr
= DISPLAYPORT_ATTR_WARNING
;
389 #ifdef USE_BATTERY_CONTINUE
390 // Show warning if battery is not fresh and battery continue is active
392 tfp_sprintf(warningText
, "BATTERY CONTINUE");
393 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
396 #endif // USE_BATTERY_CONTINUE
398 // Show warning if battery is not fresh
399 if (osdWarnGetState(OSD_WARNING_BATTERY_NOT_FULL
) && !(ARMING_FLAG(ARMED
) || ARMING_FLAG(WAS_EVER_ARMED
)) && (getBatteryState() == BATTERY_OK
)
400 && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage
) {
401 tfp_sprintf(warningText
, "BATT < FULL");
402 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
407 if (osdWarnGetState(OSD_WARNING_VISUAL_BEEPER
) && osdGetVisualBeeperState()) {
408 tfp_sprintf(warningText
, " * * * *");
409 *displayAttr
= DISPLAYPORT_ATTR_INFO
;
410 osdSetVisualBeeperState(false);