/* * Authors (alphabetical order) * - Andre Bernet * - Andreas Weitl * - Bertrand Songis * - Bryan J. Rentoul (Gruvin) * - Cameron Weeks * - Erez Raviv * - Gabriel Birkus * - Jean-Pierre Parisy * - Karl Szmutny * - Michael Blandford * - Michal Hlavinka * - Pat Mackenzie * - Philip Moss * - Rob Thomson * - Romolo Manfredini * - Thomas Husterer * * opentx is based on code named * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, * er9x by Erez Raviv: http://code.google.com/p/er9x/, * and the original (and ongoing) project by * Thomas Husterer, th9x: http://code.google.com/p/th9x/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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. * */ #include #include #include #include #include #include #define SWAP_DEFINED #include "opentx.h" #define CHANNEL_MAX (1024*256) void doMixerCalculations(); bool checkScreenshot(QString test); #define MODEL_RESET() \ memset(&g_model, 0, sizeof(g_model)); \ extern uint8_t s_mixer_first_run_done; \ s_mixer_first_run_done = false; \ lastFlightMode = 255; int32_t lastAct = 0; void MIXER_RESET() { memset(channelOutputs, 0, sizeof(channelOutputs)); memset(ex_chans, 0, sizeof(ex_chans)); memset(act, 0, sizeof(act)); memset(swOn, 0, sizeof(swOn)); #if !defined(CPUARM) s_last_switch_used = 0; s_last_switch_value = 0; #endif mixerCurrentFlightMode = lastFlightMode = 0; lastAct = 0; logicalSwitchesReset(); } uint16_t anaInValues[NUM_STICKS+NUM_POTS] = { 0 }; uint16_t anaIn(uint8_t chan) { if (chan < NUM_STICKS+NUM_POTS) return anaInValues[chan]; else return 0; } #if !defined(PCBTARANIS) TEST(Trims, greaterTrimLink) { MODEL_RESET(); setTrimValue(1, 0, TRIM_EXTENDED_MAX+3); // link to FP3 trim setTrimValue(3, 0, 32); EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(1, 0), 0), 32); } TEST(Trims, chainedTrims) { MODEL_RESET(); setTrimValue(0, 0, 32); setTrimValue(1, 0, TRIM_EXTENDED_MAX+1); // link to FP0 trim setTrimValue(2, 0, TRIM_EXTENDED_MAX+2); // link to FP1 trim EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(0, 2), 0), 32); } TEST(Trims, infiniteChainedTrims) { MODEL_RESET(); setTrimValue(0, 0, 32); setTrimValue(1, 0, TRIM_EXTENDED_MAX+3); // link to FP3 trim setTrimValue(2, 0, TRIM_EXTENDED_MAX+2); // link to FP1 trim setTrimValue(3, 0, TRIM_EXTENDED_MAX+3); // link to FP2 trim EXPECT_EQ(getRawTrimValue(getTrimFlightPhase(0, 2), 0), 32); } #endif TEST(Trims, CopyTrimsToOffset) { MODEL_RESET(); modelDefault(0); setTrimValue(0, 1, -100); // -100 on elevator evalFunctions(); // it disables all safety channels copyTrimsToOffset(1); EXPECT_EQ(getTrimValue(0, 1), -100); // unchanged #if defined(CPUARM) EXPECT_EQ(g_model.limitData[1].offset, -195); #else EXPECT_EQ(g_model.limitData[1].offset, -200); #endif } TEST(Trims, CopySticksToOffset) { MODEL_RESET(); modelDefault(0); anaInValues[ELE_STICK] = -100; perMain(); copySticksToOffset(1); EXPECT_EQ(g_model.limitData[1].offset, -97); } TEST(outdezNAtt, test_unsigned) { lcd_clear(); lcd_outdezNAtt(0, 0, 65530, LEFT|UNSIGN); EXPECT_TRUE(checkScreenshot("unsigned")) << "Unsigned numbers will be bad displayed"; } #if !defined(PCBSKY9X) TEST(EEPROM, 100_random_writes) { eepromFile = NULL; // in memory RlcFile f; uint8_t buf[1000]; uint8_t buf2[1000]; EeFsFormat(); for(int i=0; i<100; i++) { int size = rand()%800; for(int j=0; j> 8)); \ } \ } while (0) #define CHECK_DELAY(channel, duration) \ do { \ int32_t value = chans[(channel)]; \ for (int i=1; i<=(duration); i++) { \ evalFlightModeMixes(e_perout_mode_normal, 1); \ EXPECT_EQ(chans[(channel)], value); \ } \ } while (0) #if !defined(CPUARM) TEST(Mixer, SlowOnSwitch) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[0].swtch = SWSRC_THR; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], 0); simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, +1, 250); simuSetSwitch(0, -1); CHECK_SLOW_MOVEMENT(0, -1, 250); } TEST(Mixer, SlowUpOnSwitch) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[0].swtch = SWSRC_THR; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = 0; simuSetSwitch(0, 0); evalFlightModeMixes(e_perout_mode_normal, 0); s_mixer_first_run_done = true; EXPECT_EQ(chans[0], 0); simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, +1, 250); simuSetSwitch(0, 0); evalFlightModeMixes(e_perout_mode_normal, 1); EXPECT_EQ(chans[0], 0); lastAct = 0; simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, +1, 100); } #endif TEST(Mixer, SlowOnPhase) { MODEL_RESET(); MIXER_RESET(); g_model.flightModeData[1].swtch = TR(SWSRC_THR, SWSRC_SA0); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[0].flightModes = 0x2 + 0x4 + 0x8 + 0x10 /*only enabled in phase 0*/; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; mixerCurrentFlightMode = 0; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], 0); CHECK_SLOW_MOVEMENT(0, +1, 250); mixerCurrentFlightMode = 1; CHECK_SLOW_MOVEMENT(0, -1, 250); } #if !defined(CPUARM) TEST(Mixer, SlowOnSwitchAndPhase) { MODEL_RESET(); MIXER_RESET(); g_model.flightModeData[1].swtch = TR(SWSRC_THR, SWSRC_SA0); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[0].swtch = TR(SWSRC_THR, SWSRC_SA0); #if defined(CPUARM) g_model.mixData[0].flightModes = 0x2 + 0x4 + 0x8 + 0x10 + 0x20 + 0x40 + 0x80 + 0x100 /*only enabled in phase 0*/; #else g_model.mixData[0].flightModes = 0x2 + 0x4 + 0x8 + 0x10 /*only enabled in phase 0*/; #endif g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], 0); simuSetSwitch(0, 1); mixerCurrentFlightMode = 0; CHECK_SLOW_MOVEMENT(0, +1, 250); simuSetSwitch(0, -1); mixerCurrentFlightMode = 1; CHECK_SLOW_MOVEMENT(0, -1, 250); } #endif TEST(Mixer, SlowOnSwitchSource) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = TR(MIXSRC_THR, MIXSRC_SA); g_model.mixData[0].weight = 100; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; simuSetSwitch(0, -1); CHECK_SLOW_MOVEMENT(0, -1, 250); EXPECT_EQ(chans[0], -CHANNEL_MAX); simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, +1, 500); } TEST(Mixer, SlowDisabledOnStartup) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], CHANNEL_MAX); } #if !defined(CPUARM) TEST(Mixer, SlowAndDelayOnReplace3POSSource) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_REP; g_model.mixData[0].srcRaw = MIXSRC_3POS; g_model.mixData[0].weight = 100; g_model.mixData[0].delayUp = 10; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; simuSetSwitch(3, -1); CHECK_SLOW_MOVEMENT(0, -1, 250); EXPECT_EQ(chans[0], -CHANNEL_MAX); simuSetSwitch(3, 0); CHECK_DELAY(0, 500); CHECK_SLOW_MOVEMENT(0, +1, 250/*half course*/); EXPECT_EQ(chans[0], 0); simuSetSwitch(3, 1); CHECK_DELAY(0, 500); CHECK_SLOW_MOVEMENT(0, +1, 250); } #endif #if !defined(CPUARM) TEST(Mixer, SlowOnSwitchReplace) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 50; g_model.mixData[1].destCh = 0; g_model.mixData[1].mltpx = MLTPX_REP; g_model.mixData[1].srcRaw = MIXSRC_MAX; g_model.mixData[1].weight = 100; g_model.mixData[1].swtch = SWSRC_THR; g_model.mixData[1].speedDown = SLOW_STEP*5; simuSetSwitch(0, 0); evalFlightModeMixes(e_perout_mode_normal, 1); EXPECT_EQ(chans[0], CHANNEL_MAX/2); simuSetSwitch(0, 1); evalFlightModeMixes(e_perout_mode_normal, 1); // slow is not applied, but it's better than the first mix not applied at all! EXPECT_EQ(chans[0], CHANNEL_MAX); simuSetSwitch(0, 0); evalFlightModeMixes(e_perout_mode_normal, 1); // slow is not applied, but it's better than the first mix not applied at all! EXPECT_EQ(chans[0], CHANNEL_MAX/2); } #endif #if !defined(PCBTARANIS) TEST(Mixer, NoTrimOnInactiveMix) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_Thr; g_model.mixData[0].weight = 100; g_model.mixData[0].swtch = SWSRC_THR; g_model.mixData[0].speedUp = SLOW_STEP*5; g_model.mixData[0].speedDown = SLOW_STEP*5; setTrimValue(0, 2, 256); s_mixer_first_run_done = true; simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, 1, 100); simuSetSwitch(0, -1); CHECK_SLOW_MOVEMENT(0, -1, 100); } #endif TEST(Mixer, SlowOnMultiply) { MODEL_RESET(); MIXER_RESET(); g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_MAX; g_model.mixData[0].weight = 100; g_model.mixData[1].destCh = 0; g_model.mixData[1].mltpx = MLTPX_MUL; g_model.mixData[1].srcRaw = MIXSRC_MAX; g_model.mixData[1].weight = 100; g_model.mixData[1].swtch = TR(SWSRC_THR, SWSRC_SA0); g_model.mixData[1].speedUp = SLOW_STEP*5; g_model.mixData[1].speedDown = SLOW_STEP*5; s_mixer_first_run_done = true; simuSetSwitch(0, 1); CHECK_SLOW_MOVEMENT(0, 1, 250); simuSetSwitch(0, -1); CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250); simuSetSwitch(0, 1); CHECK_NO_MOVEMENT(0, CHANNEL_MAX, 250); } TEST(Curves, LinearIntpol) { MODEL_RESET(); for (int8_t i=-2; i<=2; i++) { g_model.points[2+i] = 50*i; } EXPECT_EQ(applyCustomCurve(-1024, 0), -1024); EXPECT_EQ(applyCustomCurve(0, 0), 0); EXPECT_EQ(applyCustomCurve(1024, 0), 1024); EXPECT_EQ(applyCustomCurve(-192, 0), -192); } #if defined(HELI) && defined(PCBTARANIS) TEST(Heli, BasicTest) { MODEL_RESET(); applyDefaultTemplate(); g_model.swashR.collectiveSource = MIXSRC_Thr; g_model.swashR.type = SWASH_TYPE_120; g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_CYC1; g_model.mixData[0].weight = 100; g_model.mixData[1].destCh = 1; g_model.mixData[1].mltpx = MLTPX_ADD; g_model.mixData[1].srcRaw = MIXSRC_CYC2; g_model.mixData[1].weight = 100; g_model.mixData[2].destCh = 2; g_model.mixData[2].mltpx = MLTPX_ADD; g_model.mixData[2].srcRaw = MIXSRC_CYC3; g_model.mixData[2].weight = 100; anaInValues[ELE_STICK] = 1024; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], -CHANNEL_MAX); EXPECT_EQ(chans[1], CHANNEL_MAX/2); EXPECT_EQ(chans[2], CHANNEL_MAX/2); } TEST(Heli, Mode2Test) { MODEL_RESET(); g_eeGeneral.templateSetup = 2; applyDefaultTemplate(); g_model.swashR.collectiveSource = MIXSRC_Thr; g_model.swashR.type = SWASH_TYPE_120; g_model.mixData[0].destCh = 0; g_model.mixData[0].mltpx = MLTPX_ADD; g_model.mixData[0].srcRaw = MIXSRC_CYC1; g_model.mixData[0].weight = 100; g_model.mixData[1].destCh = 1; g_model.mixData[1].mltpx = MLTPX_ADD; g_model.mixData[1].srcRaw = MIXSRC_CYC2; g_model.mixData[1].weight = 100; g_model.mixData[2].destCh = 2; g_model.mixData[2].mltpx = MLTPX_ADD; g_model.mixData[2].srcRaw = MIXSRC_CYC3; g_model.mixData[2].weight = 100; anaInValues[ELE_STICK] = 1024; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], -CHANNEL_MAX); EXPECT_EQ(chans[1], CHANNEL_MAX/2); EXPECT_EQ(chans[2], CHANNEL_MAX/2); } #elif defined(HELI) TEST(Heli, SimpleTest) { MODEL_RESET(); applyTemplate(TMPL_HELI_SETUP); anaInValues[ELE_STICK] = 1024; evalFlightModeMixes(e_perout_mode_normal, 0); EXPECT_EQ(chans[0], -CHANNEL_MAX); EXPECT_EQ(chans[1], CHANNEL_MAX/2); EXPECT_EQ(chans[1], CHANNEL_MAX/2); } #endif void doPaint(QPainter & p) { QRgb rgb = qRgb(161, 161, 161); p.setBackground(QBrush(rgb)); p.eraseRect(0, 0, LCD_W, LCD_H); if (1) { #if !defined(PCBTARANIS) rgb = qRgb(0, 0, 0); p.setPen(rgb); p.setBrush(QBrush(rgb)); #endif #if defined(PCBTARANIS) unsigned int previousDepth = 0xFF; #endif for (int y=0; y> 4) : (lcd_buf[idx] & 0x0F); if (z) { if (z != previousDepth) { previousDepth = z; rgb = qRgb(161-(z*161)/15, 161-(z*161)/15, 161-(z*161)/15); p.setPen(rgb); p.setBrush(QBrush(rgb)); } p.drawPoint(x, y); } #endif } } } } bool checkScreenshot(QString test) { lcdRefresh(); QImage buffer(LCD_W, LCD_H, QImage::Format_RGB32); QPainter p(&buffer); doPaint(p); QString filename(QString("%1_%2x%3.png").arg(test).arg(LCD_W).arg(LCD_H)); buffer.save("/tmp/" + filename); QFile screenshot("/tmp/" + filename); if (!screenshot.open(QIODevice::ReadOnly)) return false; QFile reference("./tests/" + filename); if (!reference.open(QIODevice::ReadOnly)) return false; if (reference.readAll() != screenshot.readAll()) return false; screenshot.remove(); return true; } TEST(Lcd, Invers_0_0) { lcd_clear(); lcd_putsAtt(0, 0, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_0")); } TEST(Lcd, Invers_0_1) { lcd_clear(); lcd_putsAtt(0, 1, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_1")); } TEST(Lcd, Prec2_Left) { lcd_clear(); lcd_outdezAtt(0, 0, 2, PREC2|LEFT); EXPECT_TRUE(checkScreenshot("prec2_left")); } TEST(Lcd, Prec2_Right) { lcd_clear(); lcd_outdezAtt(LCD_W, LCD_H-FH, 2, PREC2); EXPECT_TRUE(checkScreenshot("prec2_right")); } #if defined(CPUARM) TEST(Lcd, Prec1_Dblsize_Invers) { lcd_clear(); lcd_outdezAtt(LCD_W, 10, 51, PREC1|DBLSIZE|INVERS); EXPECT_TRUE(checkScreenshot("prec1_dblsize_invers")); } #endif TEST(Lcd, Line_Wrap) { lcd_clear(); lcd_puts(LCD_W-10, 0, "TEST"); EXPECT_TRUE(checkScreenshot("line_wrap")); } #if defined(CPUARM) TEST(Lcd, Smlsize_putsStrIdx) { lcd_clear(); putsStrIdx(0, 0, "FM", 0, SMLSIZE); EXPECT_TRUE(checkScreenshot("smlsize_putsstridx")); } #endif TEST(Lcd, vline) { lcd_clear(); for (int x=0; x<100; x+=2) { lcd_vline(x, x/2, 12); } EXPECT_TRUE(checkScreenshot("vline")); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); StartEepromThread(NULL); g_menuStackPtr = 0; g_menuStack[0] = menuMainView; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }