/* * This file is part of Cleanflight. * * Cleanflight is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cleanflight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cleanflight. If not, see . */ #include #include #include #include extern "C" { #include "platform.h" #include "build/debug.h" #include "blackbox/blackbox.h" #include "blackbox/blackbox_io.h" #include "common/time.h" #include "config/config.h" #include "config/feature.h" #include "drivers/osd_symbols.h" #include "drivers/persistent.h" #include "drivers/serial.h" #include "fc/core.h" #include "fc/rc_controls.h" #include "fc/rc_modes.h" #include "fc/runtime_config.h" #include "flight/gps_rescue.h" #include "flight/imu.h" #include "flight/mixer.h" #include "flight/pid.h" #include "io/beeper.h" #include "io/gps.h" #include "osd/osd.h" #include "osd/osd_elements.h" #include "osd/osd_warnings.h" #include "pg/pg.h" #include "pg/pg_ids.h" #include "pg/rx.h" #include "sensors/acceleration.h" #include "sensors/battery.h" #include "rx/rx.h" void osdUpdate(timeUs_t currentTimeUs); void osdFormatTime(char * buff, osd_timer_precision_e precision, timeUs_t time); int osdConvertTemperatureToSelectedUnit(int tempInDegreesCelcius); uint16_t rssi; attitudeEulerAngles_t attitude; float rMat[3][3]; pidProfile_t *currentPidProfile; int16_t debug[DEBUG16_VALUE_COUNT]; float rcData[MAX_SUPPORTED_RC_CHANNEL_COUNT]; uint8_t GPS_numSat; uint16_t GPS_distanceToHome; int16_t GPS_directionToHome; uint32_t GPS_distanceFlownInCm; int32_t GPS_coord[2]; gpsSolutionData_t gpsSol; float motor[8]; linkQualitySource_e linkQualitySource; acc_t acc; PG_REGISTER(batteryConfig_t, batteryConfig, PG_BATTERY_CONFIG, 0); PG_REGISTER(blackboxConfig_t, blackboxConfig, PG_BLACKBOX_CONFIG, 0); PG_REGISTER(systemConfig_t, systemConfig, PG_SYSTEM_CONFIG, 0); PG_REGISTER(pilotConfig_t, pilotConfig, PG_PILOT_CONFIG, 0); PG_REGISTER(gpsRescueConfig_t, gpsRescueConfig, PG_GPS_RESCUE, 0); PG_REGISTER(imuConfig_t, imuConfig, PG_IMU_CONFIG, 0); PG_REGISTER(gpsConfig_t, gpsConfig, PG_GPS_CONFIG, 0); timeUs_t simulationTime = 0; batteryState_e simulationBatteryState; uint8_t simulationBatteryCellCount; uint16_t simulationBatteryVoltage; uint32_t simulationBatteryAmperage; uint32_t simulationMahDrawn; float simulationWhDrawn; int32_t simulationAltitude; int32_t simulationVerticalSpeed; uint16_t simulationCoreTemperature; bool simulationGpsHealthy; } uint32_t simulationFeatureFlags = FEATURE_GPS; /* #define DEBUG_OSD */ #include "unittest_macros.h" #include "unittest_displayport.h" #include "gtest/gtest.h" void setDefaultSimulationState() { memset(osdElementConfigMutable(), 0, sizeof(osdElementConfig_t)); osdConfigMutable()->enabled_stats = 0; osdConfigMutable()->framerate_hz = 12; rssi = 1024; simulationBatteryState = BATTERY_OK; simulationBatteryCellCount = 4; simulationBatteryVoltage = 1680; simulationBatteryAmperage = 0; simulationMahDrawn = 0; simulationWhDrawn = 0; simulationAltitude = 0; simulationVerticalSpeed = 0; simulationCoreTemperature = 0; simulationGpsHealthy = false; rcData[PITCH] = 1500; osdFlyTime = 0; DISABLE_ARMING_FLAG(ARMED); } void osdRefresh() { while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } simulationTime += 0.1e6; } /* * Performs a test of the OSD actions on arming. * (reused throughout the test suite) */ void doTestArm(bool testEmpty = true) { // given // craft has been armed ENABLE_ARMING_FLAG(ARMED); simulationTime += 5e6; // when // sufficient OSD updates have been called osdRefresh(); // then // arming alert displayed displayPortTestBufferSubstring(12, 7, "ARMED"); // given // armed alert times out (0.5 seconds) simulationTime += 0.5e6; // when // sufficient OSD updates have been called osdRefresh(); // then // arming alert disappears #ifdef DEBUG_OSD displayPortTestPrint(); #endif if (testEmpty) { displayPortTestBufferIsEmpty(); } } /* * Auxiliary function. Test is there're stats that must be shown */ bool isSomeStatEnabled(void) { return (osdConfigMutable()->enabled_stats != 0); } /* * Performs a test of the OSD actions on disarming. * (reused throughout the test suite) */ void doTestDisarm() { // given // craft is disarmed after having been armed DISABLE_ARMING_FLAG(ARMED); // when // sufficient OSD updates have been called osdRefresh(); // then // post flight statistics displayed if (isSomeStatEnabled()) { unsigned enabledStats = osdConfigMutable()->enabled_stats; unsigned count = 0; while (enabledStats) { count += enabledStats & 1; enabledStats >>= 1; } displayPortTestBufferSubstring(2, 7 - count / 2, " --- STATS ---"); } } void setupStats(void) { // this set of enabled post flight statistics osdStatSetState(OSD_STAT_MAX_SPEED, true); osdStatSetState(OSD_STAT_MIN_BATTERY, true); osdStatSetState(OSD_STAT_MIN_RSSI, true); osdStatSetState(OSD_STAT_MAX_CURRENT, false); osdStatSetState(OSD_STAT_USED_MAH, false); osdStatSetState(OSD_STAT_MAX_ALTITUDE, true); osdStatSetState(OSD_STAT_BLACKBOX, false); osdStatSetState(OSD_STAT_END_BATTERY, true); osdStatSetState(OSD_STAT_RTC_DATE_TIME, true); osdStatSetState(OSD_STAT_MAX_DISTANCE, true); osdStatSetState(OSD_STAT_FLIGHT_DISTANCE, true); osdStatSetState(OSD_STAT_BLACKBOX_NUMBER, false); osdStatSetState(OSD_STAT_MAX_G_FORCE, false); osdStatSetState(OSD_STAT_MAX_ESC_TEMP, false); osdStatSetState(OSD_STAT_MAX_ESC_RPM, false); } void simulateFlight(void) { // these conditions occur during flight rssi = 1024; gpsSol.groundSpeed = 500; GPS_distanceToHome = 20; GPS_distanceFlownInCm = 2000; simulationBatteryVoltage = 1580; simulationAltitude = 100; simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } rssi = 512; gpsSol.groundSpeed = 800; GPS_distanceToHome = 50; GPS_distanceFlownInCm = 10000; simulationBatteryVoltage = 1470; simulationAltitude = 150; simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } rssi = 256; gpsSol.groundSpeed = 200; GPS_distanceToHome = 100; GPS_distanceFlownInCm = 20000; simulationBatteryVoltage = 1520; simulationAltitude = 200; simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } rssi = 256; gpsSol.groundSpeed = 800; GPS_distanceToHome = 100; GPS_distanceFlownInCm = 10000; simulationBatteryVoltage = 1470; simulationAltitude = 200; // converts to 6.56168 feet which rounds to 6.6 in imperial units stats test simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } simulationBatteryVoltage = 1520; simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } rssi = 256; gpsSol.groundSpeed = 800; GPS_distanceToHome = 1150; GPS_distanceFlownInCm = 1050000; simulationBatteryVoltage = 1470; simulationAltitude = 200; simulationTime += 1e6; while (osdUpdateCheck(simulationTime, 0)) { osdUpdate(simulationTime); simulationTime += 10; } simulationBatteryVoltage = 1520; simulationTime += 1e6; } class OsdTest : public ::testing::Test { protected: static void SetUpTestCase() { displayPortTestInit(); } virtual void SetUp() { setDefaultSimulationState(); } virtual void TearDown() { // Clean up the armed state without showing stats at the end of a test osdConfigMutable()->enabled_stats = 0; doTestDisarm(); } }; /* * Tests initialisation of the OSD and the power on splash screen. */ TEST_F(OsdTest, TestInit) { // given // display port is initialised displayPortTestInit(); // and // default state values are set setDefaultSimulationState(); // and // this battery configuration (used for battery voltage elements) batteryConfigMutable()->vbatmincellvoltage = 330; batteryConfigMutable()->vbatmaxcellvoltage = 430; // when // OSD is initialised osdInit(&testDisplayPort, OSD_DISPLAYPORT_DEVICE_AUTO); osdRefresh(); // then // display buffer should contain splash screen displayPortTestBufferSubstring(7, 8, "MENU:THR MID"); displayPortTestBufferSubstring(11, 9, "+ YAW LEFT"); displayPortTestBufferSubstring(11, 10, "+ PITCH UP"); // when // splash screen timeout has elapsed simulationTime += 4e6; osdRefresh(); // then // display buffer should be empty #ifdef DEBUG_OSD displayPortTestPrint(); #endif displayPortTestBufferIsEmpty(); } /* * Tests visibility of the ARMED notification after arming. */ TEST_F(OsdTest, TestArm) { doTestArm(); } /* * Tests display and timeout of the post flight statistics screen after disarming. */ TEST_F(OsdTest, TestDisarm) { doTestArm(); doTestDisarm(); // given // post flight stats times out (60 seconds) simulationTime += 60e6; // when // sufficient OSD updates have been called osdRefresh(); // then // post flight stats screen disappears #ifdef DEBUG_OSD displayPortTestPrint(); #endif displayPortTestBufferIsEmpty(); } /* * Tests disarming and immediately rearming clears post flight stats and shows ARMED notification. */ TEST_F(OsdTest, TestDisarmWithImmediateRearm) { doTestArm(); doTestDisarm(); doTestArm(); } /* * Tests dismissing the statistics screen with pitch stick after disarming. */ TEST_F(OsdTest, TestDisarmWithDismissStats) { doTestArm(); doTestDisarm(); // given // sticks have been moved rcData[PITCH] = 1800; // when // sufficient OSD updates have been called osdRefresh(); // then // post flight stats screen disappears #ifdef DEBUG_OSD displayPortTestPrint(); #endif displayPortTestBufferIsEmpty(); } /* * Tests the calculation of timing in statistics */ TEST_F(OsdTest, TestStatsTiming) { // given osdStatSetState(OSD_STAT_RTC_DATE_TIME, true); osdStatSetState(OSD_STAT_TIMER_1, true); osdStatSetState(OSD_STAT_TIMER_2, true); // and // this timer 1 configuration osdConfigMutable()->timers[OSD_TIMER_1] = OSD_TIMER(OSD_TIMER_SRC_TOTAL_ARMED, OSD_TIMER_PREC_HUNDREDTHS, 0); // and // this timer 2 configuration osdConfigMutable()->timers[OSD_TIMER_2] = OSD_TIMER(OSD_TIMER_SRC_LAST_ARMED, OSD_TIMER_PREC_SECOND, 0); // and // this RTC time dateTime_t dateTime; dateTime.year = 2017; dateTime.month = 11; dateTime.day = 19; dateTime.hours = 10; dateTime.minutes = 12; dateTime.seconds = 0; dateTime.millis = 0; rtcSetDateTime(&dateTime); // when // the craft is armed doTestArm(); // and // these conditions occur during flight simulationTime += 1e6; osdRefresh(); // and // the craft is disarmed doTestDisarm(); // and // the craft is armed again doTestArm(); // and // these conditions occur during flight simulationTime += 1e6; osdRefresh(); // and // the craft is disarmed doTestDisarm(); // then // statistics screen should display the following int row = 7; displayPortTestBufferSubstring(2, row++, "2017-11-19 10:12:"); displayPortTestBufferSubstring(2, row++, "TOTAL ARM : 00:13.61"); displayPortTestBufferSubstring(2, row++, "LAST ARM : 00:01"); } /* * Tests the calculation of statistics with imperial unit output. */ TEST_F(OsdTest, TestStatsImperial) { // given setupStats(); // and // using imperial unit system osdConfigMutable()->units = UNIT_IMPERIAL; // and // a GPS fix is present stateFlags |= GPS_FIX | GPS_FIX_HOME; // when // the craft is armed doTestArm(); // and simulateFlight(); // and // the craft is disarmed doTestDisarm(); // then // statistics screen should display the following int row = 5; displayPortTestBufferSubstring(2, row++, "MAX ALTITUDE : 6.6%c", SYM_FT); displayPortTestBufferSubstring(2, row++, "MAX SPEED : 17"); displayPortTestBufferSubstring(2, row++, "MAX DISTANCE : 3772%c", SYM_FT); displayPortTestBufferSubstring(2, row++, "FLIGHT DISTANCE : 6.52%c", SYM_MILES); displayPortTestBufferSubstring(2, row++, "MIN BATTERY : 14.70%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "END BATTERY : 15.20%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "MIN RSSI : 25%%"); } /* * Tests the calculation of statistics with metric unit output. * (essentially an abridged version of the previous test */ TEST_F(OsdTest, TestStatsMetric) { // given setupStats(); // and // using metric unit system osdConfigMutable()->units = UNIT_METRIC; // when // the craft is armed doTestArm(); // and simulateFlight(); // and // the craft is disarmed doTestDisarm(); // then // statistics screen should display the following int row = 5; displayPortTestBufferSubstring(2, row++, "MAX ALTITUDE : 2.0%c", SYM_M); displayPortTestBufferSubstring(2, row++, "MAX SPEED : 28"); displayPortTestBufferSubstring(2, row++, "MAX DISTANCE : 1.15%c", SYM_KM); displayPortTestBufferSubstring(2, row++, "FLIGHT DISTANCE : 10.5%c", SYM_KM); displayPortTestBufferSubstring(2, row++, "MIN BATTERY : 14.70%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "END BATTERY : 15.20%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "MIN RSSI : 25%%"); } /* * Tests the calculation of statistics with metric unit output. * (essentially an abridged version of the previous test */ TEST_F(OsdTest, TestStatsMetricDistanceUnits) { // given setupStats(); // and // using metric unit system osdConfigMutable()->units = UNIT_METRIC; // when // the craft is armed doTestArm(); // and simulateFlight(); // and // the craft is disarmed doTestDisarm(); // then // statistics screen should display the following int row = 5; displayPortTestBufferSubstring(2, row++, "MAX ALTITUDE : 2.0%c", SYM_M); displayPortTestBufferSubstring(2, row++, "MAX SPEED : 28"); displayPortTestBufferSubstring(2, row++, "MAX DISTANCE : 1.15%c", SYM_KM); displayPortTestBufferSubstring(2, row++, "FLIGHT DISTANCE : 10.5%c", SYM_KM); displayPortTestBufferSubstring(2, row++, "MIN BATTERY : 14.70%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "END BATTERY : 15.20%c", SYM_VOLT); displayPortTestBufferSubstring(2, row++, "MIN RSSI : 25%%"); } /* * Tests activation of alarms and element flashing. */ TEST_F(OsdTest, TestAlarms) { // given sensorsSet(SENSOR_GPS); // and // the following OSD elements are visible osdElementConfigMutable()->item_pos[OSD_RSSI_VALUE] = OSD_POS(8, 1) | OSD_PROFILE_1_FLAG; osdElementConfigMutable()->item_pos[OSD_MAIN_BATT_VOLTAGE] = OSD_POS(12, 1) | OSD_PROFILE_1_FLAG; osdElementConfigMutable()->item_pos[OSD_ITEM_TIMER_1] = OSD_POS(20, 1) | OSD_PROFILE_1_FLAG; osdElementConfigMutable()->item_pos[OSD_ITEM_TIMER_2] = OSD_POS(1, 1) | OSD_PROFILE_1_FLAG; osdElementConfigMutable()->item_pos[OSD_REMAINING_TIME_ESTIMATE] = OSD_POS(1, 2) | OSD_PROFILE_1_FLAG; osdElementConfigMutable()->item_pos[OSD_ALTITUDE] = OSD_POS(23, 7) | OSD_PROFILE_1_FLAG; // and // this set of alarm values osdConfigMutable()->rssi_alarm = 20; osdConfigMutable()->cap_alarm = 2200; osdConfigMutable()->alt_alarm = 100; // meters osdAnalyzeActiveElements(); // and // this timer 1 configuration osdConfigMutable()->timers[OSD_TIMER_1] = OSD_TIMER(OSD_TIMER_SRC_ON, OSD_TIMER_PREC_HUNDREDTHS, 5); EXPECT_EQ(OSD_TIMER_SRC_ON, OSD_TIMER_SRC(osdConfig()->timers[OSD_TIMER_1])); EXPECT_EQ(OSD_TIMER_PREC_HUNDREDTHS, OSD_TIMER_PRECISION(osdConfig()->timers[OSD_TIMER_1])); EXPECT_EQ(5, OSD_TIMER_ALARM(osdConfig()->timers[OSD_TIMER_1])); // and // this timer 2 configuration osdConfigMutable()->timers[OSD_TIMER_2] = OSD_TIMER(OSD_TIMER_SRC_TOTAL_ARMED, OSD_TIMER_PREC_SECOND, 2); EXPECT_EQ(OSD_TIMER_SRC_TOTAL_ARMED, OSD_TIMER_SRC(osdConfig()->timers[OSD_TIMER_2])); EXPECT_EQ(OSD_TIMER_PREC_SECOND, OSD_TIMER_PRECISION(osdConfig()->timers[OSD_TIMER_2])); EXPECT_EQ(2, OSD_TIMER_ALARM(osdConfig()->timers[OSD_TIMER_2])); // and // using the metric unit system osdConfigMutable()->units = UNIT_METRIC; // when // time is passing by simulationTime += 60e6; osdRefresh(); // and // the craft is armed doTestArm(false); simulationTime += 70e6; osdRefresh(); // then // no elements should flash as all values are out of alarm range for (int i = 0; i < 30; i++) { // Check for visibility every 100ms, elements should always be visible simulationTime += 0.1e6; osdRefresh(); #ifdef DEBUG_OSD printf("%d\n", i); #endif displayPortTestBufferSubstring(1, 1, "%c01:", SYM_FLY_M); // only test the minute part of the timer displayPortTestBufferSubstring(8, 1, "%c99", SYM_RSSI); displayPortTestBufferSubstring(12, 1, "%c16.8%c", SYM_BATT_FULL, SYM_VOLT); displayPortTestBufferSubstring(20, 1, "%c04:", SYM_ON_M); // only test the minute part of the timer displayPortTestBufferSubstring(23, 7, "%c0.0%c", SYM_ALTITUDE, SYM_M); } // when // all values are out of range rssi = 128; simulationBatteryState = BATTERY_CRITICAL; simulationBatteryVoltage = 1350; simulationAltitude = 12000; simulationMahDrawn = 999999; simulationTime += 60e6; osdRefresh(); // then // elements showing values in alarm range should flash simulationTime += 1000000; simulationTime -= simulationTime % 1000000; timeUs_t startTime = simulationTime; for (int i = 0; i < 15; i++) { // Blinking should happen at 2Hz simulationTime = startTime + i*0.25e6; osdRefresh(); #ifdef DEBUG_OSD printf("%d\n", i); displayPortTestPrint(); #endif if (i % 2 == 1) { displayPortTestBufferSubstring(8, 1, "%c12", SYM_RSSI); displayPortTestBufferSubstring(12, 1, "%c13.5%c", SYM_MAIN_BATT, SYM_VOLT); displayPortTestBufferSubstring(1, 1, "%c02:", SYM_FLY_M); // only test the minute part of the timer displayPortTestBufferSubstring(20, 1, "%c05:", SYM_ON_M); // only test the minute part of the timer displayPortTestBufferSubstring(23, 7, "%c120.0%c", SYM_ALTITUDE, SYM_M); } else { displayPortTestBufferIsEmpty(); } } } /* * Tests the RSSI OSD element. */ TEST_F(OsdTest, TestElementRssi) { // given osdElementConfigMutable()->item_pos[OSD_RSSI_VALUE] = OSD_POS(8, 1) | OSD_PROFILE_1_FLAG; osdConfigMutable()->rssi_alarm = 0; osdAnalyzeActiveElements(); // when rssi = 1024; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(8, 1, "%c99", SYM_RSSI); // when rssi = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(8, 1, "%c 0", SYM_RSSI); // when rssi = 512; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(8, 1, "%c50", SYM_RSSI); } /* * Tests the instantaneous battery current OSD element. */ TEST_F(OsdTest, TestElementAmperage) { // given osdElementConfigMutable()->item_pos[OSD_CURRENT_DRAW] = OSD_POS(1, 12) | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // when simulationBatteryAmperage = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 12, " 0.00%c", SYM_AMP); // when simulationBatteryAmperage = 2156; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 12, " 21.56%c", SYM_AMP); // when simulationBatteryAmperage = 12345; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 12, "123.45%c", SYM_AMP); } /* * Tests the battery capacity drawn OSD element. */ TEST_F(OsdTest, TestElementMahDrawn) { // given osdElementConfigMutable()->item_pos[OSD_MAH_DRAWN] = OSD_POS(1, 11) | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // when simulationMahDrawn = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 11, " 0%c", SYM_MAH); // when simulationMahDrawn = 4; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 11, " 4%c", SYM_MAH); // when simulationMahDrawn = 15; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 11, " 15%c", SYM_MAH); // when simulationMahDrawn = 246; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 11, " 246%c", SYM_MAH); // when simulationMahDrawn = 1042; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 11, "1042%c", SYM_MAH); } /* * Tests the instantaneous electrical power OSD element. */ TEST_F(OsdTest, TestElementPower) { // given osdElementConfigMutable()->item_pos[OSD_POWER] = OSD_POS(1, 10) | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // and simulationBatteryVoltage = 1000; // 10V // and simulationBatteryAmperage = 0; // 0A // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 10, " 0W"); // given simulationBatteryAmperage = 10; // 0.1A // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 10, " 1W"); // given simulationBatteryAmperage = 120; // 1.2A // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 10, " 12W"); // given simulationBatteryAmperage = 1230; // 12.3A // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 10, " 123W"); // given simulationBatteryAmperage = 12340; // 123.4A // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 10, "1234W"); } /* * Tests the altitude OSD element. */ TEST_F(OsdTest, TestElementAltitude) { // given osdElementConfigMutable()->item_pos[OSD_ALTITUDE] = OSD_POS(23, 7) | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // and osdConfigMutable()->units = UNIT_METRIC; sensorsClear(SENSOR_GPS); // when simulationAltitude = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c-", SYM_ALTITUDE); // when sensorsSet(SENSOR_GPS); displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c0.0%c", SYM_ALTITUDE, SYM_M); // when simulationAltitude = 247; // rounds to 2.5m displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c2.5%c", SYM_ALTITUDE, SYM_M); // when simulationAltitude = 4247; // rounds to 42.5m displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c42.5%c", SYM_ALTITUDE, SYM_M); // when simulationAltitude = -247; // rounds to -2.5m displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c-2.5%c", SYM_ALTITUDE, SYM_M); // when simulationAltitude = -70; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(23, 7, "%c-0.7%c", SYM_ALTITUDE, SYM_M); } /* * Tests the core temperature OSD element. */ TEST_F(OsdTest, TestElementCoreTemperature) { // given osdElementConfigMutable()->item_pos[OSD_CORE_TEMPERATURE] = OSD_POS(1, 8) | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // and osdConfigMutable()->units = UNIT_METRIC; // and simulationCoreTemperature = 0; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 8, "C%c 0%c", SYM_TEMPERATURE, SYM_C); // given simulationCoreTemperature = 33; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 8, "C%c 33%c", SYM_TEMPERATURE, SYM_C); // given osdConfigMutable()->units = UNIT_IMPERIAL; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(1, 8, "C%c 91%c", SYM_TEMPERATURE, SYM_F); } /* * Tests the battery notifications shown on the warnings OSD element. */ TEST_F(OsdTest, TestElementWarningsBattery) { // given osdElementConfigMutable()->item_pos[OSD_WARNINGS] = OSD_POS(9, 10) | OSD_PROFILE_1_FLAG; osdConfigMutable()->enabledWarnings = 0; // disable all warnings osdWarnSetState(OSD_WARNING_BATTERY_WARNING, true); osdWarnSetState(OSD_WARNING_BATTERY_CRITICAL, true); osdWarnSetState(OSD_WARNING_BATTERY_NOT_FULL, true); osdAnalyzeActiveElements(); // and batteryConfigMutable()->vbatfullcellvoltage = 410; // and // 4S battery simulationBatteryCellCount = 4; // and // used battery simulationBatteryVoltage = ((batteryConfig()->vbatmaxcellvoltage - 20) * simulationBatteryCellCount) - 1; simulationBatteryState = BATTERY_OK; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); // Delay as the warnings are flashing simulationTime += 1000000; simulationTime -= simulationTime % 1000000; osdRefresh(); // then displayPortTestBufferSubstring(9, 10, "BATT < FULL"); // given // full battery simulationBatteryVoltage = 1680; simulationBatteryState = BATTERY_OK; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(9, 10, " "); // given // low battery simulationBatteryVoltage = 1400; simulationBatteryState = BATTERY_WARNING; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); // Delay as the warnings are flashing simulationTime += 1000000; simulationTime -= simulationTime % 1000000; simulationTime += 0.25e6; osdRefresh(); // then displayPortTestBufferSubstring(9, 10, "LOW BATTERY "); // given // critical battery simulationBatteryVoltage = 1320; simulationBatteryState = BATTERY_CRITICAL; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); // Delay as the warnings are flashing simulationTime += 1000000; simulationTime -= simulationTime % 1000000; simulationTime += 0.25e6; osdRefresh(); // then displayPortTestBufferSubstring(9, 10, " LAND NOW "); // given // full battery simulationBatteryVoltage = ((batteryConfig()->vbatmaxcellvoltage - 20) * simulationBatteryCellCount); simulationBatteryState = BATTERY_OK; // when displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(9, 10, " "); // TODO } /* * Tests the time string formatting function with a series of precision settings and time values. */ TEST_F(OsdTest, TestFormatTimeString) { char buff[OSD_ELEMENT_BUFFER_LENGTH]; /* Seconds precision, 0 us */ osdFormatTime(buff, OSD_TIMER_PREC_SECOND, 0); EXPECT_EQ(0, strcmp("00:00", buff)); /* Seconds precision, 0.9 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_SECOND, 0.9e6); EXPECT_EQ(0, strcmp("00:00", buff)); /* Seconds precision, 10 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_SECOND, 10e6); EXPECT_EQ(0, strcmp("00:10", buff)); /* Seconds precision, 1 minute */ osdFormatTime(buff, OSD_TIMER_PREC_SECOND, 60e6); EXPECT_EQ(0, strcmp("01:00", buff)); /* Seconds precision, 1 minute 59 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_SECOND, 119e6); EXPECT_EQ(0, strcmp("01:59", buff)); /* Hundredths precision, 0 us */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 0); EXPECT_EQ(0, strcmp("00:00.00", buff)); /* Hundredths precision, 10 milliseconds (one 100th of a second) */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 10e3); EXPECT_EQ(0, strcmp("00:00.01", buff)); /* Hundredths precision, 0.9 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 0.9e6); EXPECT_EQ(0, strcmp("00:00.90", buff)); /* Hundredths precision, 10 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 10e6); EXPECT_EQ(0, strcmp("00:10.00", buff)); /* Hundredths precision, 1 minute */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 60e6); EXPECT_EQ(0, strcmp("01:00.00", buff)); /* Hundredths precision, 1 minute 59 seconds */ osdFormatTime(buff, OSD_TIMER_PREC_HUNDREDTHS, 119e6); EXPECT_EQ(0, strcmp("01:59.00", buff)); } TEST_F(OsdTest, TestConvertTemperatureUnits) { /* In Celsius */ osdConfigMutable()->units = UNIT_METRIC; EXPECT_EQ(osdConvertTemperatureToSelectedUnit(40), 40); /* In Fahrenheit */ osdConfigMutable()->units = UNIT_IMPERIAL; EXPECT_EQ(osdConvertTemperatureToSelectedUnit(40), 104); /* In Fahrenheit with rounding */ osdConfigMutable()->units = UNIT_IMPERIAL; EXPECT_EQ(osdConvertTemperatureToSelectedUnit(41), 106); } TEST_F(OsdTest, TestGpsElements) { // given osdElementConfigMutable()->item_pos[OSD_GPS_SATS] = OSD_POS(2, 4) | OSD_PROFILE_1_FLAG; sensorsSet(SENSOR_GPS); osdAnalyzeActiveElements(); // when simulationGpsHealthy = false; gpsSol.numSat = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then // Sat indicator should blink and show "NC" simulationTime += 1000000; simulationTime -= simulationTime % 1000000; timeUs_t startTime = simulationTime; for (int i = 0; i < 15; i++) { // Blinking should happen at 2Hz simulationTime = startTime + i*0.25e6; osdRefresh(); if (i % 2 == 1) { displayPortTestBufferSubstring(2, 4, "%c%cNC", SYM_SAT_L, SYM_SAT_R); } else { displayPortTestBufferIsEmpty(); } } // when simulationGpsHealthy = true; gpsSol.numSat = 0; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then // Sat indicator should blink and show "0" simulationTime += 1000000; simulationTime -= simulationTime % 1000000; startTime = simulationTime; for (int i = 0; i < 15; i++) { // Blinking should happen at 2Hz simulationTime = startTime + i*0.25e6; osdRefresh(); if (i % 2 == 1) { displayPortTestBufferSubstring(2, 4, "%c%c 0", SYM_SAT_L, SYM_SAT_R); } else { displayPortTestBufferIsEmpty(); } } // when simulationGpsHealthy = true; gpsSol.numSat = 10; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then // Sat indicator should show "10" without flashing for (int i = 0; i < 15; i++) { // Blinking should happen at 2Hz simulationTime += 0.2e6; osdRefresh(); displayPortTestBufferSubstring(2, 4, "%c%c10", SYM_SAT_L, SYM_SAT_R); } } TEST_F(OsdTest, TestHdPositioning) { // given // Try to round-trip the OSD_POS macro with an "HD location" osdElementConfigMutable()->item_pos[OSD_RSSI_VALUE] = OSD_POS(53, 0) | OSD_PROFILE_1_FLAG; osdConfigMutable()->rssi_alarm = 0; // Also try to round-trip a raw value matching one generated by the Configurator. osdElementConfigMutable()->item_pos[OSD_CURRENT_DRAW] = 3125 | OSD_PROFILE_1_FLAG; osdAnalyzeActiveElements(); // when simulationBatteryAmperage = 0; rssi = 1024; displayClearScreen(&testDisplayPort, DISPLAY_CLEAR_WAIT); osdRefresh(); // then displayPortTestBufferSubstring(53, 0, "%c99", SYM_RSSI); displayPortTestBufferSubstring(53, 1, " 0.00%c", SYM_AMP); } // STUBS extern "C" { bool featureIsEnabled(uint32_t f) { return simulationFeatureFlags & f; } void beeperConfirmationBeeps(uint8_t) {} bool isModeActivationConditionPresent(boxId_e) { return false; } bool IS_RC_MODE_ACTIVE(boxId_e) { return false; } uint32_t micros() { return simulationTime; } uint32_t millis() { return micros() / 1000; } bool isBeeperOn() { return false; } bool airmodeIsEnabled() { return false; } uint8_t getCurrentPidProfileIndex() { return 0; } uint8_t getCurrentControlRateProfileIndex() { return 0; } batteryState_e getBatteryState() { return simulationBatteryState; } uint8_t getBatteryCellCount() { return simulationBatteryCellCount; } uint16_t getBatteryVoltage() { return simulationBatteryVoltage; } uint16_t getBatteryAverageCellVoltage() { return simulationBatteryVoltage / simulationBatteryCellCount; } int32_t getAmperage() { return simulationBatteryAmperage; } int32_t getMAhDrawn() { return simulationMahDrawn; } float getWhDrawn() { return simulationWhDrawn; } int32_t getEstimatedAltitudeCm() { return simulationAltitude; } int32_t getEstimatedVario() { return simulationVerticalSpeed; } int32_t blackboxGetLogNumber() { return 0; } bool isBlackboxDeviceWorking() { return true; } bool isBlackboxDeviceFull() { return false; } bool isSerialTransmitBufferEmpty(const serialPort_t *) { return false; } void serialWrite(serialPort_t *, uint8_t) {} bool cmsDisplayPortRegister(displayPort_t *) { return false; } uint16_t getRssi(void) { return rssi; } uint8_t getRssiPercent(void) { return scaleRange(rssi, 0, RSSI_MAX_VALUE, 0, 100); } uint16_t rxGetLinkQuality(void) { return LINK_QUALITY_MAX_VALUE; } uint16_t getCoreTemperatureCelsius(void) { return simulationCoreTemperature; } bool isFlipOverAfterCrashActive(void) { return false; } float pidItermAccelerator(void) { return 1.0; } uint8_t getMotorCount(void){ return 4; } bool areMotorsRunning(void){ return true; } bool pidOsdAntiGravityActive(void) { return false; } bool failsafeIsActive(void) { return false; } bool gpsRescueIsConfigured(void) { return false; } bool gpsIsHealthy(void) { return simulationGpsHealthy; } int8_t calculateThrottlePercent(void) { return 0; } uint32_t persistentObjectRead(persistentObjectId_e) { return 0; } void persistentObjectWrite(persistentObjectId_e, uint32_t) {} bool isUpright(void) { return true; } float getMotorOutputLow(void) { return 1000.0; } float getMotorOutputHigh(void) { return 2047.0; } void schedulerIgnoreTaskStateTime(void) { } void schedulerIgnoreTaskExecRate(void) { } void schedulerIgnoreTaskExecTime(void) { } bool schedulerGetIgnoreTaskExecTime() { return false; } void schedulerSetNextStateTime(timeDelta_t) {} }