From 3ebc6bb86c594c9b23ffe2789d852bb8e50281f3 Mon Sep 17 00:00:00 2001 From: "David S. Beach" Date: Sun, 13 Dec 2015 11:22:33 -0500 Subject: [PATCH] Ported simulator enhancements to next --- .../firmwares/er9x/simulator/er9xsimulator.h | 4 + .../ersky9x/simulator/ersky9xsimulator.h | 4 + .../opentx/simulator/opentxsimulator.cpp | 30 + .../opentx/simulator/opentxsimulator.h | 4 + .../src/simulation/simulatordialog-horus.ui | 699 +++--- .../src/simulation/simulatordialog-taranis.ui | 496 ++-- .../src/simulation/simulatordialoghorus.cpp | 3 + .../src/simulation/simulatordialogtaranis.cpp | 3 + companion/src/simulation/simulatorinterface.h | 4 + companion/src/simulation/telemetrysimu.cpp | 854 ++++++- companion/src/simulation/telemetrysimu.h | 142 +- companion/src/simulation/telemetrysimu.ui | 2083 ++++++++++++++--- 12 files changed, 3364 insertions(+), 962 deletions(-) diff --git a/companion/src/firmwares/er9x/simulator/er9xsimulator.h b/companion/src/firmwares/er9x/simulator/er9xsimulator.h index f6fb7fad6..1f0ed0944 100644 --- a/companion/src/firmwares/er9x/simulator/er9xsimulator.h +++ b/companion/src/firmwares/er9x/simulator/er9xsimulator.h @@ -54,6 +54,10 @@ class Er9xSimulator : public SimulatorInterface { virtual void sendTelemetry(uint8_t * data, unsigned int len) {}; + virtual uint8_t getSensorInstance(uint16_t id) { return 0; }; + + virtual uint16_t getSensorRatio(uint16_t id) { return 0; }; + virtual void setTrainerInput(unsigned int inputNumber, int16_t value) {}; protected: diff --git a/companion/src/firmwares/ersky9x/simulator/ersky9xsimulator.h b/companion/src/firmwares/ersky9x/simulator/ersky9xsimulator.h index 57737257d..3303f9ae7 100644 --- a/companion/src/firmwares/ersky9x/simulator/ersky9xsimulator.h +++ b/companion/src/firmwares/ersky9x/simulator/ersky9xsimulator.h @@ -54,6 +54,10 @@ class Ersky9xSimulator : public SimulatorInterface { virtual void sendTelemetry(uint8_t * data, unsigned int len) {}; + virtual uint8_t getSensorInstance(uint16_t id) { return 0; }; + + virtual uint16_t getSensorRatio(uint16_t id) { return 0; }; + virtual void setTrainerInput(unsigned int inputNumber, int16_t value) {}; protected: diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 19efaa450..b8f7503fb 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -607,6 +607,36 @@ void OpenTxSimulator::sendTelemetry(::uint8_t * data, unsigned int len) #endif } +uint8_t OpenTxSimulator::getSensorInstance(uint16_t id) +{ +#if defined(FRSKY_SPORT) + for (int i = 0; iid == id) { + return sensor->instance; + } + } + } +#endif + return 0; +} + +uint16_t OpenTxSimulator::getSensorRatio(uint16_t id) +{ +#if defined(FRSKY_SPORT) + for (int i = 0; iid == id) { + return sensor->custom.ratio; + } + } + } +#endif + return 0; +} + void OpenTxSimulator::setTrainerInput(unsigned int inputNumber, ::int16_t value) { #define SETTRAINER_IMPORT diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.h b/companion/src/firmwares/opentx/simulator/opentxsimulator.h index 95cdafd39..8e20de520 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.h +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.h @@ -130,6 +130,10 @@ class DLLEXPORT OpenTxSimulator : public SimulatorInterface { virtual void sendTelemetry(uint8_t * data, unsigned int len); + virtual uint8_t getSensorInstance(uint16_t id); + + virtual uint16_t getSensorRatio(uint16_t id); + virtual void setTrainerInput(unsigned int inputNumber, int16_t value); virtual void installTraceHook(void (*callback)(const char *)); diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui index 09db52e34..71e92ce06 100644 --- a/companion/src/simulation/simulatordialog-horus.ui +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -16,13 +16,13 @@ 0 - + 960 850 - + 960 850 @@ -36,345 +36,13 @@ :/icon.png:/icon.png - - 5 - - - 5 - - - 5 - - + 5 0 - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 700 - 5 - - - - - - - - true - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - 0 - - - - Horus Simulator - - - - 5 - - - 5 - - - 0 - - - - - QLayout::SetFixedSize - - - 0 - - - - - - 0 - 0 - - - - - 147 - 292 - - - - - 147 - 292 - - - - true - - - background:url(:/images/simulator/Horus/left.png); - - - - - - - - 0 - 0 - - - - - 480 - 9 - - - - - 480 - 9 - - - - - 5 - - - - background:url(:/images/simulator/Horus/top.png) - - - - - - - - 0 - 0 - - - - - 480 - 272 - - - - - 480 - 272 - - - - - 5 - - - - - - - - - 0 - 0 - - - - - 480 - 11 - - - - - 480 - 11 - - - - - 5 - false - - - - background:url(:/images/simulator/Horus/bottom.png) - - - - - - - - 0 - 0 - - - - - 24 - 292 - - - - - 24 - 292 - - - - background:url(:/images/simulator/Horus/led.png) - - - - - - - - 0 - 0 - - - - - 27 - 292 - - - - - 27 - 292 - - - - background:url(:/images/simulator/Horus/border-right.png) - - - - - - - - 0 - 0 - - - - - 147 - 292 - - - - - 147 - 292 - - - - background:url(:/images/simulator/Horus/right.png) - - - - - - - - - Qt::Horizontal - - - - 20 - 292 - - - - - - - - Qt::Horizontal - - - - 20 - 292 - - - - - - - - - O&utputs - - - - - - - - - - - - - Ou&tputs2 - - - - - - - - - - - - - &Gvars - - - - - 10 - 10 - 691 - 231 - - - - - - - - + @@ -404,16 +72,7 @@ 2 - - 5 - - - 5 - - - 5 - - + 5 @@ -1779,6 +1438,354 @@ QPushButton:checked { + + + + + + PF4 - Telemetry Simulator + + + + + + + PF5 - Trainer Simulator + + + + + + + PF6 - Debug Console Window + + + + + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + Horus Simulator + + + + 5 + + + 5 + + + 0 + + + + + QLayout::SetFixedSize + + + 0 + + + + + + 0 + 0 + + + + + 147 + 292 + + + + + 147 + 292 + + + + true + + + background:url(:/images/simulator/Horus/left.png); + + + + + + + + 0 + 0 + + + + + 480 + 9 + + + + + 480 + 9 + + + + + 5 + + + + background:url(:/images/simulator/Horus/top.png) + + + + + + + + 0 + 0 + + + + + 480 + 272 + + + + + 480 + 272 + + + + + 5 + + + + + + + + + 0 + 0 + + + + + 480 + 11 + + + + + 480 + 11 + + + + + 5 + false + + + + background:url(:/images/simulator/Horus/bottom.png) + + + + + + + + 0 + 0 + + + + + 24 + 292 + + + + + 24 + 292 + + + + background:url(:/images/simulator/Horus/led.png) + + + + + + + + 0 + 0 + + + + + 27 + 292 + + + + + 27 + 292 + + + + background:url(:/images/simulator/Horus/border-right.png) + + + + + + + + 0 + 0 + + + + + 147 + 292 + + + + + 147 + 292 + + + + background:url(:/images/simulator/Horus/right.png) + + + + + + + + + Qt::Horizontal + + + + 20 + 292 + + + + + + + + Qt::Horizontal + + + + 20 + 292 + + + + + + + + + O&utputs + + + + + + + + + + + + + Ou&tputs2 + + + + + + + + + + + + + &Gvars + + + + + 10 + 10 + 691 + 231 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 700 + 5 + + + + diff --git a/companion/src/simulation/simulatordialog-taranis.ui b/companion/src/simulation/simulatordialog-taranis.ui index e2f693cb8..e29d017c4 100644 --- a/companion/src/simulation/simulatordialog-taranis.ui +++ b/companion/src/simulation/simulatordialog-taranis.ui @@ -30,251 +30,13 @@ :/icon.png:/icon.png - - 5 - - - 5 - - - 5 - - + 5 0 - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 570 - 5 - - - - - - - - true - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - 0 - - - - Taranis Simulator - - - - - - QLayout::SetMinimumSize - - - 0 - - - - - - 0 - 0 - - - - - 120 - 208 - - - - true - - - background:url(:/images/simulator/Taranis/x9l0.png); - - - - - - - - 0 - 0 - - - - - 424 - 38 - - - - background:url(:/images/simulator/Taranis/x9t0.png) - - - - - - - - 0 - 0 - - - - - 120 - 208 - - - - background:url(:/images/simulator/Taranis/x9r0.png) - - - - - - - - 0 - 0 - - - - - 424 - 128 - - - - - - - - - 0 - 0 - - - - - 424 - 42 - - - - background:url(:/images/simulator/Taranis/x9b0.png) - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal - - - - 438 - 6 - - - - - - - - - - - Outputs - - - - - - - - - - - - - Outputs2 - - - - - - - - - - - - - Gvars - - - - - - - - - - + @@ -1740,6 +1502,260 @@ QPushButton:checked { + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 570 + 5 + + + + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + Taranis Simulator + + + + + + QLayout::SetMinimumSize + + + 0 + + + + + + 0 + 0 + + + + + 120 + 208 + + + + true + + + background:url(:/images/simulator/Taranis/x9l0.png); + + + + + + + + 0 + 0 + + + + + 424 + 38 + + + + background:url(:/images/simulator/Taranis/x9t0.png) + + + + + + + + 0 + 0 + + + + + 120 + 208 + + + + background:url(:/images/simulator/Taranis/x9r0.png) + + + + + + + + 0 + 0 + + + + + 424 + 128 + + + + + + + + + 0 + 0 + + + + + 424 + 42 + + + + background:url(:/images/simulator/Taranis/x9b0.png) + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + 438 + 6 + + + + + + + + + + + Outputs + + + + + + + + + + + + + Outputs2 + + + + + + + + + + + + + Gvars + + + + + + + + + + + + + + + PF4 - Telemetry Simulator + + + + + + + PF5 - Trainer Simulator + + + + + + + PF6 - Debug Console Output + + + + + diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp index c18b6755a..72faddd8c 100644 --- a/companion/src/simulation/simulatordialoghorus.cpp +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -70,6 +70,9 @@ SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->teleSim, SIGNAL(released()), this, SLOT(openTelemetrySimulator())); + connect(ui->trainerSim, SIGNAL(released()), this, SLOT(openTrainerSimulator())); + connect(ui->debugConsole, SIGNAL(released()), this, SLOT(openDebugOutput())); } SimulatorDialogHorus::~SimulatorDialogHorus() diff --git a/companion/src/simulation/simulatordialogtaranis.cpp b/companion/src/simulation/simulatordialogtaranis.cpp index dc67dfa6c..609bb7c40 100644 --- a/companion/src/simulation/simulatordialogtaranis.cpp +++ b/companion/src/simulation/simulatordialogtaranis.cpp @@ -77,6 +77,9 @@ SimulatorDialogTaranis::SimulatorDialogTaranis(QWidget * parent, SimulatorInterf connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->teleSim, SIGNAL(released()), this, SLOT(openTelemetrySimulator())); + connect(ui->trainerSim, SIGNAL(released()), this, SLOT(openTrainerSimulator())); + connect(ui->debugConsole, SIGNAL(released()), this, SLOT(openDebugOutput())); } SimulatorDialogTaranis::~SimulatorDialogTaranis() diff --git a/companion/src/simulation/simulatorinterface.h b/companion/src/simulation/simulatorinterface.h index a19753ba6..5a97cae2c 100644 --- a/companion/src/simulation/simulatorinterface.h +++ b/companion/src/simulation/simulatorinterface.h @@ -99,6 +99,10 @@ class SimulatorInterface virtual void sendTelemetry(uint8_t * data, unsigned int len) = 0; + virtual uint8_t getSensorInstance(uint16_t id) = 0; + + virtual uint16_t getSensorRatio(uint16_t id) = 0; + virtual void setTrainerInput(unsigned int inputNumber, int16_t value) = 0; virtual void installTraceHook(void (*callback)(const char *)) = 0; diff --git a/companion/src/simulation/telemetrysimu.cpp b/companion/src/simulation/telemetrysimu.cpp index 4d57f54c9..bea1c64d8 100644 --- a/companion/src/simulation/telemetrysimu.cpp +++ b/companion/src/simulation/telemetrysimu.cpp @@ -11,30 +11,174 @@ TelemetrySimulator::TelemetrySimulator(QWidget * parent, SimulatorInterface * si { ui->setupUi(this); + QPoint dialogCenter = mapToGlobal(rect().center()); + QPoint parentWindowCenter = parent->window()->mapToGlobal( + parent->window()->rect().center()); + move(parentWindowCenter - dialogCenter); + timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); - timer->start(100); + + logTimer = new QTimer(this); + connect(logTimer, SIGNAL(timeout()), this, SLOT(onLogTimerEvent())); + + connect(ui->Simulate, SIGNAL(clicked(bool)), this, SLOT(onSimulateToggled(bool))); + connect(ui->loadLogFile, SIGNAL(released()), this, SLOT(onLoadLogFile())); + connect(ui->play, SIGNAL(released()), this, SLOT(onPlay())); + connect(ui->rewind, SIGNAL(clicked()), this, SLOT(onRewind())); + connect(ui->stepForward, SIGNAL(clicked()), this, SLOT(onStepForward())); + connect(ui->stepBack, SIGNAL(clicked()), this, SLOT(onStepBack())); + connect(ui->stop, SIGNAL(clicked()), this, SLOT(onStop())); + connect(ui->positionIndicator, SIGNAL(valueChanged(int)), this, SLOT(onPositionIndicatorChanged(int))); + connect(ui->replayRate, SIGNAL(valueChanged(int)), this, SLOT(onReplayRateChanged(int))); + + ui->A1->setSpecialValueText(" "); + ui->A2->setSpecialValueText(" "); + ui->A3->setSpecialValueText(" "); + ui->A4->setSpecialValueText(" "); + ui->rpm->setSpecialValueText(" "); + ui->fuel->setSpecialValueText(" "); + + ui->rxbt_ratio->setEnabled(false); + ui->A1_ratio->setEnabled(false); + ui->A2_ratio->setEnabled(false); + + logPlayback = new LogPlaybackController(ui); } TelemetrySimulator::~TelemetrySimulator() { timer->stop(); + logTimer->stop(); delete ui; } +void TelemetrySimulator::onSimulateToggled(bool isChecked) +{ + if (isChecked) { + timer->start(10); + } + else { + timer->stop(); + } +} + + +void TelemetrySimulator::onLogTimerEvent() +{ + logPlayback->stepForward(false); +} + + void TelemetrySimulator::onTimerEvent() { - if (ui->Simulate->isChecked()) { - generateTelemetryFrame(); + generateTelemetryFrame(); +} + +void TelemetrySimulator::onLoadLogFile() +{ + onStop(); // in case we are in playback mode + logPlayback->loadLogFile(); +} + +void TelemetrySimulator::onPlay() +{ + if (logPlayback->isReady()) { + logTimer->start(logPlayback->logFrequency * 1000 / SPEEDS[ui->replayRate->value()]); + logPlayback->play(); + } +} + +void TelemetrySimulator::onRewind() +{ + if (logPlayback->isReady()) { + logTimer->stop(); + logPlayback->rewind(); + } +} + +void TelemetrySimulator::onStepForward() +{ + if (logPlayback->isReady()) { + logTimer->stop(); + logPlayback->stepForward(true); + } +} + +void TelemetrySimulator::onStepBack() +{ + if (logPlayback->isReady()) { + logTimer->stop(); + logPlayback->stepBack(); + } +} + +void TelemetrySimulator::onStop() +{ + if (logPlayback->isReady()) { + logTimer->stop(); + logPlayback->stop(); + } +} + +void TelemetrySimulator::onPositionIndicatorChanged(int value) +{ + if (logPlayback->isReady()) { + logPlayback->updatePositionLabel(value); + logPlayback->setUiDataValues(); + } +} + +void TelemetrySimulator::onReplayRateChanged(int value) +{ + if (logTimer->isActive()) { + logTimer->setInterval(logPlayback->logFrequency * 1000 / SPEEDS[ui->replayRate->value()]); } } void TelemetrySimulator::closeEvent(QCloseEvent *event) { - ui->Simulate->setChecked(false); + timer->stop(); + logTimer->stop(); event->accept(); } +void TelemetrySimulator::showEvent(QShowEvent *event) +{ + + ui->rxbt_inst->setText(QString::number(simulator->getSensorInstance(BATT_ID))); + ui->rxbt_ratio->setValue(simulator->getSensorRatio(BATT_ID) / 10.0); + ui->rssi_inst->setText(QString::number(simulator->getSensorInstance(RSSI_ID))); + ui->swr_inst->setText(QString::number(simulator->getSensorInstance(SWR_ID))); + ui->a1_inst->setText(QString::number(simulator->getSensorInstance(ADC1_ID))); + ui->A1_ratio->setValue(simulator->getSensorRatio(ADC1_ID) / 10.0); + ui->a2_inst->setText(QString::number(simulator->getSensorInstance(ADC2_ID))); + ui->A2_ratio->setValue(simulator->getSensorRatio(ADC2_ID) / 10.0); + ui->a3_inst->setText(QString::number(simulator->getSensorInstance(A3_FIRST_ID))); + ui->a4_inst->setText(QString::number(simulator->getSensorInstance(A4_FIRST_ID))); + ui->t1_inst->setText(QString::number(simulator->getSensorInstance(T1_FIRST_ID))); + ui->t2_inst->setText(QString::number(simulator->getSensorInstance(T2_FIRST_ID))); + ui->rpm_inst->setText(QString::number(simulator->getSensorInstance(RPM_FIRST_ID))); + ui->fuel_inst->setText(QString::number(simulator->getSensorInstance(FUEL_FIRST_ID))); + ui->aspd_inst->setText(QString::number(simulator->getSensorInstance(AIR_SPEED_FIRST_ID))); + ui->vvspd_inst->setText(QString::number(simulator->getSensorInstance(VARIO_FIRST_ID))); + ui->valt_inst->setText(QString::number(simulator->getSensorInstance(ALT_FIRST_ID))); + ui->fasv_inst->setText(QString::number(simulator->getSensorInstance(VFAS_FIRST_ID))); + ui->fasc_inst->setText(QString::number(simulator->getSensorInstance(CURR_FIRST_ID))); + ui->cells_inst->setText(QString::number(simulator->getSensorInstance(CELLS_FIRST_ID))); + ui->gpsa_inst->setText(QString::number(simulator->getSensorInstance(GPS_ALT_FIRST_ID))); + ui->gpss_inst->setText(QString::number(simulator->getSensorInstance(GPS_SPEED_FIRST_ID))); + ui->gpsc_inst->setText(QString::number(simulator->getSensorInstance(GPS_COURS_FIRST_ID))); + ui->gpst_inst->setText(QString::number(simulator->getSensorInstance(GPS_TIME_DATE_FIRST_ID))); + ui->gpsll_inst->setText(QString::number(simulator->getSensorInstance(GPS_LONG_LATI_FIRST_ID))); + ui->accx_inst->setText(QString::number(simulator->getSensorInstance(ACCX_FIRST_ID))); + ui->accy_inst->setText(QString::number(simulator->getSensorInstance(ACCY_FIRST_ID))); + ui->accz_inst->setText(QString::number(simulator->getSensorInstance(ACCZ_FIRST_ID))); + + ui->Simulate->setChecked(true); + onSimulateToggled(true); // not sure why this doesn't fire automatically +} + void setSportPacketCrc(uint8_t * packet) { short crc = 0; @@ -63,133 +207,164 @@ void TelemetrySimulator::generateTelemetryFrame() static int item = 0; bool ok = true; uint8_t buffer[FRSKY_SPORT_PACKET_SIZE]; + static FlvssEmulator *flvss = new FlvssEmulator(); + static GPSEmulator *gps = new GPSEmulator(); + memset(buffer, 0, sizeof(buffer)); - switch(item++) { + switch (item++) { case 0: - if (ui->Rssi->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, RSSI_ID, LIMIT(0, ui->Rssi->text().toInt(&ok, 0), 0xFF)); - break; +#if defined(XJT_VERSION_ID) + generateSportPacket(buffer, 1, DATA_FRAME, XJT_VERSION_ID, 11); +#endif + break; case 1: -#if defined(XJT_VERSION_ID) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, XJT_VERSION_ID, 11); -#endif + if (ui->rxbt->text().length()) { + generateSportPacket(buffer, ui->rxbt_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, BATT_ID, LIMIT(0, ui->rxbt->value() * 255.0 / ui->rxbt_ratio->value(), 0xFFFFFFFF)); + } break; case 2: - if (ui->Swr->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, SWR_ID, LIMIT(0, ui->Swr->text().toInt(&ok, 0), 0xFFFF)); + if (ui->Rssi->text().length()) + generateSportPacket(buffer, ui->rssi_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, RSSI_ID, LIMIT(0, ui->Rssi->text().toInt(&ok, 0), 0xFF)); break; case 3: - if (ui->A1->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ADC1_ID, LIMIT(0, ui->A1->text().toInt(&ok, 0), 0xFF)); + if (ui->Swr->text().length()) + generateSportPacket(buffer, ui->swr_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, SWR_ID, LIMIT(0, ui->Swr->text().toInt(&ok, 0), 0xFFFF)); break; case 4: - if (ui->A2->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ADC2_ID, LIMIT(0, ui->A2->text().toInt(&ok, 0), 0xFF)); + if (ui->A1->value() > 0) + generateSportPacket(buffer, ui->a1_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ADC1_ID, LIMIT(0, ui->A1->value() * 255.0 / ui->A1_ratio->value(), 0xFF)); break; case 5: - if (ui->A3->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, A3_FIRST_ID, LIMIT(0, ui->A3->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->A2->value() > 0) + generateSportPacket(buffer, ui->a2_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ADC2_ID, LIMIT(0, ui->A2->value() * 255.0 / ui->A2_ratio->value(), 0xFF)); break; case 6: - if (ui->A4->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, A4_FIRST_ID, LIMIT(0, ui->A4->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->A3->value() > 0) + generateSportPacket(buffer, ui->a3_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, A3_FIRST_ID, LIMIT(0, ui->A3->value() * 100.0, 0xFFFFFFFF)); break; case 7: - if (ui->T1->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, T1_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->T1->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->A4->value() > 0) + generateSportPacket(buffer, ui->a4_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, A4_FIRST_ID, LIMIT(0, ui->A4->value() * 100.0, 0xFFFFFFFF)); break; case 8: - if (ui->T2->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, T2_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->T2->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->T1->value() != 0) + generateSportPacket(buffer, ui->t1_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, T1_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->T1->value(), 0x7FFFFFFF)); break; case 9: - if (ui->rpm->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, RPM_FIRST_ID, LIMIT(0, ui->rpm->text().toInt(&ok, 0), 0xFFFF)); + if (ui->T2->value() != 0) + generateSportPacket(buffer, ui->t2_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, T2_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->T2->value(), 0x7FFFFFFF)); break; case 10: - if (ui->fuel->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, FUEL_FIRST_ID, LIMIT(0, ui->fuel->text().toInt(&ok, 0), 0xFFFF)); + if (ui->rpm->value() > 0) + generateSportPacket(buffer, ui->rpm_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, RPM_FIRST_ID, LIMIT(0, ui->rpm->value(), 0xFFFF)); break; case 11: - if (ui->aspeed->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, AIR_SPEED_FIRST_ID, LIMIT(0, ui->aspeed->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->fuel->value() > 0) + generateSportPacket(buffer, ui->fuel_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, FUEL_FIRST_ID, LIMIT(0, ui->fuel->value(), 0xFFFF)); break; case 12: - if (ui->vspeed->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, VARIO_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->vspeed->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->vspeed->value() != 0) + generateSportPacket(buffer, ui->vvspd_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, VARIO_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->vspeed->value() * 100.0, 0x7FFFFFFF)); break; case 13: - if (ui->valt->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ALT_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->valt->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->valt->value() != 0) + generateSportPacket(buffer, ui->valt_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ALT_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->valt->value() * 100.0, 0x7FFFFFFF)); break; case 14: - if (ui->accx->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ACCX_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accx->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->vfas->value() != 0) + generateSportPacket(buffer, ui->fasv_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, VFAS_FIRST_ID, LIMIT(0, ui->vfas->value() * 100.0, 0xFFFFFFFF)); break; case 15: - if (ui->accy->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ACCY_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accy->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->curr->value() != 0) + generateSportPacket(buffer, ui->fasc_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, CURR_FIRST_ID, LIMIT(0, ui->curr->value() * 10.0, 0xFFFFFFFF)); break; case 16: - if (ui->accz->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, ACCZ_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accz->text().toInt(&ok, 0), 0x7FFFFFFF)); + double cellValues[FlvssEmulator::MAXCELLS]; + if (ui->cell1->value() > 0.009) { // ??? cell1 returning non-zero value when spin box is zero! + cellValues[0] = ui->cell1->value(); + cellValues[1] = ui->cell2->value(); + cellValues[2] = ui->cell3->value(); + cellValues[3] = ui->cell4->value(); + cellValues[4] = ui->cell5->value(); + cellValues[5] = ui->cell6->value(); + generateSportPacket(buffer, ui->cells_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, CELLS_FIRST_ID, flvss->setAllCells_GetNextPair(cellValues)); + } + else { + cellValues[0] = 0; + flvss->setAllCells_GetNextPair(cellValues); + } break; case 17: - if (ui->vfas->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, VFAS_FIRST_ID, LIMIT(0, ui->vfas->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->aspeed->value() > 0) + generateSportPacket(buffer, ui->aspd_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, AIR_SPEED_FIRST_ID, LIMIT(0, ui->aspeed->value() * 10.0, 0xFFFFFFFF)); break; case 18: - if (ui->curr->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, CURR_FIRST_ID, LIMIT(0, ui->curr->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->gps_alt->value() != 0) { + gps->setGPSAltitude(ui->gps_alt->value()); + generateSportPacket(buffer, ui->gpsa_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, GPS_ALT_FIRST_ID, gps->getNextPacketData(GPS_ALT_FIRST_ID)); + } break; case 19: - if (ui->cells->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, CELLS_FIRST_ID, LIMIT(0, ui->cells->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->gps_speed->value() > 0) { + gps->setGPSSpeedKMH(ui->gps_speed->value()); + generateSportPacket(buffer, ui->gpss_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, GPS_SPEED_FIRST_ID, gps->getNextPacketData(GPS_SPEED_FIRST_ID)); + } break; case 20: - if (ui->gps_alt->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, GPS_ALT_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->gps_alt->text().toInt(&ok, 0), 0x7FFFFFFF)); + if (ui->gps_course->value() != 0) { + gps->setGPSCourse(ui->gps_course->value()); + generateSportPacket(buffer, ui->gpsc_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, GPS_COURS_FIRST_ID, gps->getNextPacketData(GPS_COURS_FIRST_ID)); + } break; case 21: - if (ui->gps_speed->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, GPS_SPEED_FIRST_ID, LIMIT(0, ui->gps_speed->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->gps_time->text().length()) { + gps->setGPSDateTime(ui->gps_time->text()); + generateSportPacket(buffer, ui->gpst_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, GPS_TIME_DATE_FIRST_ID, gps->getNextPacketData(GPS_TIME_DATE_FIRST_ID)); + } break; case 22: - if (ui->gps_course->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, GPS_COURS_FIRST_ID, LIMIT(0, ui->gps_course->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->gps_latlon->text().length()) { + gps->setGPSLatLon(ui->gps_latlon->text()); + generateSportPacket(buffer, ui->gpsll_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, GPS_LONG_LATI_FIRST_ID, gps->getNextPacketData(GPS_LONG_LATI_FIRST_ID)); + } break; case 23: - if (ui->gps_time->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, GPS_TIME_DATE_FIRST_ID, LIMIT(0, ui->gps_time->text().toInt(&ok, 0), 0xFFFFFFFF)); - break; + if (ui->accx->value() != 0) + generateSportPacket(buffer, ui->accx_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ACCX_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accx->value() * 100.0, 0x7FFFFFFF)); + break; case 24: - if (ui->gps_latlon->text().length()) - generateSportPacket(buffer, INSTANCE, DATA_FRAME, GPS_LONG_LATI_FIRST_ID, LIMIT(0, ui->gps_latlon->text().toInt(&ok, 0), 0xFFFFFFFF)); + if (ui->accy->value() != 0) + generateSportPacket(buffer, ui->accy_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ACCY_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accy->value() * 100.0, 0x7FFFFFFF)); + break; + + case 25: + if (ui->accz->value() != 0) + generateSportPacket(buffer, ui->accz_inst->text().toInt(&ok, 0) - 1, DATA_FRAME, ACCZ_FIRST_ID, LIMIT(-0x7FFFFFFF, ui->accz->value() * 100.0, 0x7FFFFFFF)); break; default: @@ -202,3 +377,566 @@ void TelemetrySimulator::generateTelemetryFrame() else onTimerEvent(); } + +uint32_t TelemetrySimulator::FlvssEmulator::encodeCellPair(uint8_t cellNum, uint8_t firstCellNo, double cell1, double cell2) +{ + uint16_t cell1Data = cell1 * 500.0; + uint16_t cell2Data = cell2 * 500.0; + uint32_t cellData = 0; + + cellData = cell2Data & 0x0FFF; + cellData <<= 12; + cellData |= cell1Data & 0x0FFF; + cellData <<= 4; + cellData |= cellNum & 0x0F; + cellData <<= 4; + cellData |= firstCellNo & 0x0F; + + return cellData; +} + +void TelemetrySimulator::FlvssEmulator::encodeAllCells() +{ + cellData1 = encodeCellPair(numCells, 0, cellFloats[0], cellFloats[1]); + if (numCells > 2) cellData2 = encodeCellPair(numCells, 2, cellFloats[2], cellFloats[3]); else cellData2 = 0; + if (numCells > 4) cellData3 = encodeCellPair(numCells, 4, cellFloats[4], cellFloats[5]); else cellData3 = 0; +} + +void TelemetrySimulator::FlvssEmulator::splitIntoCells(double totalVolts) +{ + numCells = qFloor((totalVolts / 3.7) + .5); + double avgVolts = totalVolts / numCells; + double remainder = (totalVolts - (avgVolts * numCells)); + for (uint32_t i = 0; (i < numCells) && ( i < MAXCELLS); i++) { + cellFloats[i] = avgVolts; + } + for (uint32_t i = numCells; i < MAXCELLS; i++) { + cellFloats[i] = 0; + } + cellFloats[0] += remainder; + numCells = numCells > MAXCELLS ? MAXCELLS : numCells; // force into valid cell count in case of input out of range +} + +uint32_t TelemetrySimulator::FlvssEmulator::setAllCells_GetNextPair(double cellValues[6]) +{ + numCells = 0; + for (uint32_t i = 0; i < MAXCELLS; i++) { + if ((i == 0) && (cellValues[0] > 4.2)) { + splitIntoCells(cellValues[0]); + break; + } + if (cellValues[i] > 0) { + cellFloats[i] = cellValues[i]; + numCells++; + } + else { + // zero marks the last cell + for (uint32_t x = i; x < MAXCELLS; x++) { + cellFloats[x] = 0; + } + break; + } + } + + // encode the double values into telemetry format + encodeAllCells(); + + // return the value for the current pair + uint32_t cellData = 0; + if (nextCellNum >= numCells) { + nextCellNum = 0; + } + switch (nextCellNum) { + case 0: + cellData = cellData1; + break; + case 2: + cellData = cellData2; + break; + case 4: + cellData = cellData3; + break; + } + nextCellNum += 2; + return cellData; +} + +TelemetrySimulator::GPSEmulator::GPSEmulator() +{ + lat = 0; + lon = 0; + dt = QDateTime::currentDateTime(); + sendLat = true; + sendDate = true; +} + + +uint32_t TelemetrySimulator::GPSEmulator::encodeLatLon(double latLon, bool isLat) +{ + uint32_t data = (uint32_t)((latLon < 0 ? -latLon : latLon) * 60 * 10000) & 0x3FFFFFFF; + if (isLat == false) { + data |= 0x80000000; + } + if (latLon < 0) { + data |= 0x40000000; + } + return data; +} + +uint32_t TelemetrySimulator::GPSEmulator::encodeDateTime(uint8_t yearOrHour, uint8_t monthOrMinute, uint8_t dayOrSecond, bool isDate) +{ + uint32_t data = yearOrHour; + data <<= 8; + data |= monthOrMinute; + data <<= 8; + data |= dayOrSecond; + data <<= 8; + if (isDate == true) { + data |= 0xFF; + } + return data; +} + +uint32_t TelemetrySimulator::GPSEmulator::getNextPacketData(uint32_t packetType) +{ + switch (packetType) { + case GPS_LONG_LATI_FIRST_ID: + sendLat = !sendLat; + return sendLat ? encodeLatLon(lat, TRUE) : encodeLatLon(lon, FALSE); + break; + case GPS_TIME_DATE_FIRST_ID: + sendDate = !sendDate; + return sendDate ? encodeDateTime(dt.date().year() - 2000, dt.date().month(), dt.date().day(), true) : encodeDateTime(dt.time().hour(), dt.time().minute(), dt.time().second(), false); + break; + case GPS_ALT_FIRST_ID: + return (uint32_t) (altitude * 100); + break; + case GPS_SPEED_FIRST_ID: + return speedKNTS * 1000; + break; + case GPS_COURS_FIRST_ID: + return course * 100; + break; + } + return 0; +} + +void TelemetrySimulator::GPSEmulator::setGPSDateTime(QString dateTime) +{ + dt = QDateTime::currentDateTime().toTimeSpec(Qt::UTC); // default to current systemtime + if (!dateTime.startsWith('*')) { + QString format("dd-MM-yyyy hh:mm:ss"); + dt = QDateTime::fromString(dateTime, format); + } +} + +void TelemetrySimulator::GPSEmulator::setGPSLatLon(QString latLon) +{ + QStringList coords = latLon.split(","); + lat = 0.0; + lon = 0.0; + if (coords.length() > 1) + { + lat = coords[0].toDouble(); + lon = coords[1].toDouble(); + } +} + +void TelemetrySimulator::GPSEmulator::setGPSCourse(double course) +{ + this->course = course; +} + +void TelemetrySimulator::GPSEmulator::setGPSSpeedKMH(double speedKMH) +{ + this->speedKNTS = speedKMH * 0.539957; +} + +void TelemetrySimulator::GPSEmulator::setGPSAltitude(double altitude) +{ + this->altitude = altitude; +} + +TelemetrySimulator::LogPlaybackController::LogPlaybackController(Ui::TelemetrySimulator * ui) +{ + TelemetrySimulator::LogPlaybackController::ui = ui; + stepping = false; + // initialize the map - TODO: how should this be localized? + colToFuncMap.clear(); + colToFuncMap.insert("RxBt(V)", RXBT_V); + colToFuncMap.insert("RSSI(dB)", RSSI); + colToFuncMap.insert("SWR", SWR); + colToFuncMap.insert("A1", A1); + colToFuncMap.insert("A1(V)", A1); + colToFuncMap.insert("A2", A2); + colToFuncMap.insert("A2(V)", A2); + colToFuncMap.insert("A3", A3); + colToFuncMap.insert("A3(V)", A3); + colToFuncMap.insert("A4", A4); + colToFuncMap.insert("A4(V)", A4); + colToFuncMap.insert("Tmp1(@C)", T1_DEGC); + colToFuncMap.insert("Tmp1(@F)", T1_DEGF); + colToFuncMap.insert("Tmp2(@C)", T2_DEGC); + colToFuncMap.insert("Tmp2(@F)", T2_DEGF); + colToFuncMap.insert("RPM(rpm)", RPM); + colToFuncMap.insert("Fuel(%)", FUEL); + colToFuncMap.insert("VSpd(m/s)", VSPD_MS); + colToFuncMap.insert("VSpd(f/s)", VSPD_FS); + colToFuncMap.insert("Alt(ft)", ALT_FEET); + colToFuncMap.insert("Alt(m)", ALT_METERS); + colToFuncMap.insert("VFAS(V)", FASV); + colToFuncMap.insert("Curr(A)", FASC); + colToFuncMap.insert("Cels(gRe)", CELS_GRE); + colToFuncMap.insert("ASpd(kmh)", ASPD_KMH); + colToFuncMap.insert("ASpd(mph)", ASPD_MPH); + colToFuncMap.insert("GAlt(ft)", GALT_FEET); + colToFuncMap.insert("GAlt(m)", GALT_METERS); + colToFuncMap.insert("GSpd(kts)", GSPD_KNTS); + colToFuncMap.insert("GSpd(kmh)", GSPD_KMH); + colToFuncMap.insert("GSpd(mph)", GSPD_MPH); + colToFuncMap.insert("Hdg(@)", GHDG_DEG); + colToFuncMap.insert("Date", GDATE); + colToFuncMap.insert("GPS", G_LATLON); + colToFuncMap.insert("AccX(g)", ACCX); + colToFuncMap.insert("AccY(g)", ACCY); + colToFuncMap.insert("AccZ(g)", ACCZ); + + // ACCX Y and Z +} + +QDateTime TelemetrySimulator::LogPlaybackController::parseTransmittterTimestamp(QString row) +{ + QStringList rowParts = row.simplified().split(','); + if (rowParts.size() < 2) { + return QDateTime(); + } + QString datePart = rowParts[0]; + QString timePart = rowParts[1]; + QDateTime result; + QString format("yyyy-MM-dd hh:mm:ss.zzz"); // assume this format + // hour can be 'missing' + if (timePart.count(":") < 2) { + timePart = "00:" + timePart; + } + if (datePart.contains("/")) { // happens when csv is edited by Excel + format = "M/d/yyyy hh:mm:ss.z"; + } + return QDateTime::fromString(datePart + " " + timePart, format); +} + +void TelemetrySimulator::LogPlaybackController::calcLogFrequency() +{ + // examine up to 20 rows to determine log frequency in seconds + logFrequency = 25.5; // default value + QDateTime lastTime; + for (int i = 1; (i < 20) && (i < csvRecords.count()); i++) + { + QDateTime logTime = parseTransmittterTimestamp(csvRecords[i]); + // ugh - no timespan in this Qt version + double timeDiff = (logTime.toMSecsSinceEpoch() - lastTime.toMSecsSinceEpoch()) / 1000.0; + if ((timeDiff > 0) && (timeDiff < logFrequency)) { + logFrequency = timeDiff; + } + lastTime = logTime; + } +} + +bool TelemetrySimulator::LogPlaybackController::isReady() +{ + return csvRecords.count() > 1; +} + +void TelemetrySimulator::LogPlaybackController::loadLogFile() +{ + // reset the playback ui + ui->play->setEnabled(false); + ui->rewind->setEnabled(false); + ui->stepBack->setEnabled(false); + ui->stepForward->setEnabled(false); + ui->stop->setEnabled(false); + ui->positionIndicator->setEnabled(false); + ui->replayRate->setEnabled(false); + ui->positionLabel->setText("Row #\nTimestamp"); + + // clear existing data + csvRecords.clear(); + + QString logFileNameAndPath = QFileDialog::getOpenFileName(NULL, tr("Log File"), ".", tr("LOG Files (*.csv)")); + QFileInfo fileInfo(logFileNameAndPath); + QFile file(logFileNameAndPath); + if (!file.open(QIODevice::ReadOnly)) { + ui->logFileLabel->setText(tr("ERROR - invalid file")); + return; + } + while (!file.atEnd()) { + QByteArray line = file.readLine(); + csvRecords.append(line.simplified()); + } + if (csvRecords.count() > 1) { + columnNames.clear(); + QStringList keys = csvRecords[0].split(','); + // override the first two column names + keys[0] = "LogDate"; + keys[1] = "LogTime"; + Q_FOREACH(QString key, keys) { + columnNames.append(key.simplified()); + } + ui->play->setEnabled(true); + ui->rewind->setEnabled(true); + ui->stepBack->setEnabled(true); + ui->stepForward->setEnabled(true); + ui->stop->setEnabled(true); + ui->positionIndicator->setEnabled(true); + ui->replayRate->setEnabled(true); + supportedCols.clear(); + recordIndex = 1; + calcLogFrequency(); + } + ui->logFileLabel->setText(QFileInfo(logFileNameAndPath).fileName()); + // iterate through all known mappings and add those that are used + QMapIterator it(colToFuncMap); + while (it.hasNext()) { + it.next(); + addColumnHash(it.key(), it.value()); + } + rewind(); + return; +} + +void TelemetrySimulator::LogPlaybackController::addColumnHash(QString key, CONVERT_TYPE functionIndex) +{ + DATA_TO_FUNC_XREF dfx; + if (columnNames.contains(key)) { + dfx.functionIndex = functionIndex; + dfx.dataIndex = columnNames.indexOf(key); + supportedCols.append(dfx); + } +} + +void TelemetrySimulator::LogPlaybackController::play() +{ +} + +void TelemetrySimulator::LogPlaybackController::stop() +{ +} + +void TelemetrySimulator::LogPlaybackController::rewind() +{ + stepping = true; + recordIndex = 1; + ui->stop->setChecked(true); + updatePositionLabel(-1); + setUiDataValues(); + stepping = false; +} + +void TelemetrySimulator::LogPlaybackController::stepForward(bool focusOnStop) +{ + stepping = true; + if (recordIndex < (csvRecords.count() - 1)) { + recordIndex++; + if (focusOnStop) { + ui->stop->setChecked(true); + } + updatePositionLabel(-1); + setUiDataValues(); + } + else { + rewind(); // always loop at the end + } + stepping = false; +} + +void TelemetrySimulator::LogPlaybackController::stepBack() +{ + stepping = true; + if (recordIndex > 1) { + recordIndex--; + ui->stop->setChecked(true); + updatePositionLabel(-1); + setUiDataValues(); + } + stepping = false; +} + +double TelemetrySimulator::LogPlaybackController::convertFeetToMeters(QString input) +{ + double meters100 = input.toDouble() * 0.3048; + return qFloor(meters100 + .005); +} + +QString TelemetrySimulator::LogPlaybackController::convertGPSDate(QString input) +{ + QStringList dateTime = input.simplified().split(' '); + if (dateTime.size() < 2) { + return ""; // invalid format + } + QStringList dateParts = dateTime[0].split('-'); // input as yy-mm-dd + if (dateParts.size() < 3) { + return ""; // invalid format + } + // output is dd-MM-yyyy hh:mm:ss + QString localDateString = dateParts[2] + "-" + dateParts[1] + "-20" + dateParts[0] + " " + dateTime[1]; + QString format("dd-MM-yyyy hh:mm:ss"); + QDateTime utcDate = QDateTime::fromString(localDateString, format).toTimeSpec(Qt::UTC); + return utcDate.toString(format); +} + +double TelemetrySimulator::LogPlaybackController::convertDegMin(QString input) +{ + double fInput = input.mid(0, input.length() - 1).toDouble(); + double degrees = qFloor(fInput / 100.0); + double minutes = fInput - (degrees * 100); + int32_t sign = ((input.endsWith('E')) || (input.endsWith('N'))) ? 1 : -1; + return (degrees + (minutes / 60)) * sign; +} + +QString TelemetrySimulator::LogPlaybackController::convertGPS(QString input) +{ + // input format is DDmm.mmmmH DDDmm.mmmmH (longitude latitude - degrees (2 places) minutes (2 places) decimal minutes (4 places)) + QStringList lonLat = input.simplified().split(' '); + if (lonLat.count() < 2) { + return ""; // invalid format + } + double lon = convertDegMin(lonLat[0]); + double lat = convertDegMin(lonLat[1]); + return QString::number(lat) + ", " + QString::number(lon); +} + +void TelemetrySimulator::LogPlaybackController::updatePositionLabel(int32_t percentage) +{ + if ((percentage > 0) && (!stepping)) { + recordIndex = qFloor((double)csvRecords.count() / 100.0 * percentage); + if (recordIndex == 0) { + recordIndex = 1; // record 0 is column labels + } + } + // format the transmitter date info + QDateTime transmitterTimestamp = parseTransmittterTimestamp(csvRecords[recordIndex]); + QString format("yyyy-MM-dd hh:mm:ss.z"); + ui->positionLabel->setText("Row " + QString::number(recordIndex) + " of " + QString::number(csvRecords.count() - 1) + + "\n" + transmitterTimestamp.toString(format)); + if (percentage < 0) { // did we step past a threshold? + uint32_t posPercent = (recordIndex / (double)(csvRecords.count() - 1)) * 100; + ui->positionIndicator->setValue(posPercent); + } +} + +double TelemetrySimulator::LogPlaybackController::convertFahrenheitToCelsius(QString input) +{ + return (input.toDouble() - 32.0) * 0.5556; +} + +void TelemetrySimulator::LogPlaybackController::setUiDataValues() +{ + QStringList columnData = csvRecords[recordIndex].split(','); + Q_FOREACH(DATA_TO_FUNC_XREF info, supportedCols) { + if (info.dataIndex < columnData.size()) { + switch (info.functionIndex) { + case RXBT_V: + ui->rxbt->setValue(columnData[info.dataIndex].toDouble()); + break; + case RSSI: + ui->Rssi->setValue(columnData[info.dataIndex].toDouble()); + break; + case SWR: + ui->Swr->setValue(columnData[info.dataIndex].toDouble()); + break; + case A1: + ui->A1->setValue(columnData[info.dataIndex].toDouble()); + break; + case A2: + ui->A2->setValue(columnData[info.dataIndex].toDouble()); + break; + case A3: + ui->A3->setValue(columnData[info.dataIndex].toDouble()); + break; + case A4: + ui->A4->setValue(columnData[info.dataIndex].toDouble()); + break; + case T1_DEGC: + ui->T1->setValue(columnData[info.dataIndex].toDouble()); + break; + case T2_DEGC: + ui->T2->setValue(columnData[info.dataIndex].toDouble()); + break; + case T1_DEGF: + ui->T1->setValue(convertFahrenheitToCelsius(columnData[info.dataIndex])); + break; + case T2_DEGF: + ui->T2->setValue(convertFahrenheitToCelsius(columnData[info.dataIndex])); + break; + case RPM: + ui->rpm->setValue(columnData[info.dataIndex].toDouble()); + break; + case FUEL: + ui->fuel->setValue(columnData[info.dataIndex].toDouble()); + break; + case VSPD_MS: + ui->vspeed->setValue(columnData[info.dataIndex].toDouble()); + break; + case VSPD_FS: + ui->vspeed->setValue(columnData[info.dataIndex].toDouble() * 0.3048); + break; + case ALT_FEET: + ui->valt->setValue(convertFeetToMeters(columnData[info.dataIndex])); + break; + case ALT_METERS: + ui->valt->setValue(columnData[info.dataIndex].toDouble()); + break; + case FASV: + ui->vfas->setValue(columnData[info.dataIndex].toDouble()); + break; + case FASC: + ui->curr->setValue(columnData[info.dataIndex].toDouble()); + break; + case CELS_GRE: + ui->cell1->setValue(columnData[info.dataIndex].toDouble()); + break; + case ASPD_KMH: + ui->aspeed->setValue(columnData[info.dataIndex].toDouble()); + break; + case ASPD_MPH: + ui->aspeed->setValue(columnData[info.dataIndex].toDouble() * 1.60934); + break; + case GALT_FEET: + ui->gps_alt->setValue(convertFeetToMeters(columnData[info.dataIndex])); + break; + case GALT_METERS: + ui->gps_alt->setValue(columnData[info.dataIndex].toDouble()); + break; + case GSPD_KNTS: + ui->gps_speed->setValue(columnData[info.dataIndex].toDouble() * 1.852); + break; + case GSPD_KMH: + ui->gps_speed->setValue(columnData[info.dataIndex].toDouble()); + break; + case GSPD_MPH: + ui->gps_speed->setValue(columnData[info.dataIndex].toDouble() * 1.60934); + break; + case GHDG_DEG: + ui->gps_course->setValue(columnData[info.dataIndex].toDouble()); + break; + case GDATE: + ui->gps_time->setText(convertGPSDate(columnData[info.dataIndex])); + break; + case G_LATLON: + ui->gps_latlon->setText(convertGPS(columnData[info.dataIndex])); + break; + case ACCX: + ui->accx->setValue(columnData[info.dataIndex].toDouble()); + break; + case ACCY: + ui->accy->setValue(columnData[info.dataIndex].toDouble()); + break; + case ACCZ: + ui->accz->setValue(columnData[info.dataIndex].toDouble()); + break; + } + } + else { + // file is corrupt - shut down with open logs, or log format changed mid-day + } + } +} diff --git a/companion/src/simulation/telemetrysimu.h b/companion/src/simulation/telemetrysimu.h index d1383a2c3..96191d050 100644 --- a/companion/src/simulation/telemetrysimu.h +++ b/companion/src/simulation/telemetrysimu.h @@ -1,13 +1,16 @@ #ifndef telemetrysimu_h #define telemetrysimu_h - #include #include #include +#include +#include +#include + #include "simulatorinterface.h" -#define INSTANCE (ui->Instance->text().toInt(&ok,0) -1) +static double const SPEEDS[] = { 0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4, 5 }; namespace Ui { class TelemetrySimulator; @@ -22,19 +25,148 @@ class TelemetrySimulator : public QDialog explicit TelemetrySimulator(QWidget * parent, SimulatorInterface * simulator); virtual ~TelemetrySimulator(); - protected: virtual void closeEvent(QCloseEvent *event); + virtual void showEvent(QShowEvent *event); private: + class LogPlaybackController + { + public: + LogPlaybackController(Ui::TelemetrySimulator * ui); + bool isReady(); + void loadLogFile(); + void play(); + void stop(); + void rewind(); + void stepForward(bool focusOnStop); + void stepBack(); + void updatePositionLabel(int32_t percentage); + void setUiDataValues(); + double logFrequency; // in seconds + private: + enum CONVERT_TYPE { + RXBT_V, + RSSI, + SWR, + A1, + A2, + A3, + A4, + T1_DEGC, + T1_DEGF, + T2_DEGC, + T2_DEGF, + RPM, + FUEL, + VSPD_MS, + VSPD_FS, + ALT_FEET, + ALT_METERS, + FASV, + FASC, + CELS_GRE, + ASPD_KMH, + ASPD_MPH, + GALT_FEET, + GALT_METERS, + GSPD_KNTS, + GSPD_KMH, + GSPD_MPH, + GHDG_DEG, + GDATE, + G_LATLON, + ACCX, + ACCY, + ACCZ, + }; + QMap colToFuncMap; // contains all 'known' column headings and how they are to be processed + Ui::TelemetrySimulator * ui; + struct DATA_TO_FUNC_XREF { + CONVERT_TYPE functionIndex; + int32_t dataIndex; + }; + QStringList csvRecords; // contents of the log file (one string per line); + QStringList columnNames; + QList supportedCols; + int32_t recordIndex; + double convertFeetToMeters(QString input); + double convertFahrenheitToCelsius(QString input); + QString convertGPSDate(QString input); + QString convertGPS(QString input); + void addColumnHash(QString key, CONVERT_TYPE functionIndex); + double convertDegMin(QString input); + bool stepping; + QDateTime parseTransmittterTimestamp(QString row); + void calcLogFrequency(); + int32_t replayRate; + }; + +private: Ui::TelemetrySimulator * ui; QTimer * timer; + QTimer * logTimer; SimulatorInterface *simulator; - void generateTelemetryFrame(); - + TelemetrySimulator::LogPlaybackController *logPlayback; + private slots: + void onSimulateToggled(bool isChecked); void onTimerEvent(); + void onLogTimerEvent(); + void onLoadLogFile(); + void onPlay(); + void onRewind(); + void onStepForward(); + void onStepBack(); + void onStop(); + void onPositionIndicatorChanged(int value); + void onReplayRateChanged(int value); + + + private: // private classes follow + class FlvssEmulator + { + public: + uint32_t setAllCells_GetNextPair(double cellValues[6]); + static const uint32_t MAXCELLS = 6; + private: + void encodeAllCells(); + void splitIntoCells(double totalVolts); + static uint32_t encodeCellPair(uint8_t cellNum, uint8_t firstCellNo, double cell1, double cell2); + double cellFloats[6]; + uint32_t nextCellNum; + uint32_t numCells; + uint32_t cellData1; + uint32_t cellData2; + uint32_t cellData3; + uint32_t cellDataTime; + double cell[MAXCELLS]; + }; + + class GPSEmulator + { + public: + GPSEmulator(); + uint32_t getNextPacketData(uint32_t packetType); + void setGPSDateTime(QString dateTime); + void setGPSLatLon(QString latLon); + void setGPSCourse(double course); + void setGPSSpeedKMH(double speed); + void setGPSAltitude(double altitude); + private: + QDateTime dt; + bool sendLat; + bool sendDate; + double lat; + double lon; + double course; + double speedKNTS; + double altitude; // in meters + uint32_t nextDataIndex; + uint32_t encodeLatLon(double latLon, bool isLat); + uint32_t encodeDateTime(uint8_t yearOrHour, uint8_t monthOrMinute, uint8_t dayOrSecond, bool isDate); + }; }; diff --git a/companion/src/simulation/telemetrysimu.ui b/companion/src/simulation/telemetrysimu.ui index 1d9d46521..04da62728 100644 --- a/companion/src/simulation/telemetrysimu.ui +++ b/companion/src/simulation/telemetrysimu.ui @@ -6,355 +6,1812 @@ 0 0 - 331 - 384 + 759 + 539 + + + 8 + + Telemetry Simulator - - - 6 + + + + 10 + 50 + 101 + 31 + - - - - Qt::Vertical + + + 12 + + + + When enabled, sends any non-blank values as simulated telemetry data. + + + Simulate + + + + + + 120 + 10 + 621 + 111 + + + + + 8 + + + + Replay SD Log File + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 410 + 10 + 201 + 33 + + + + + QLayout::SetMinimumSize - - - 20 - 40 - - - - - - - - - - Vario Altitude + + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus - V-ALT + |> - - - - - - Vario Vertical Speed + + false - - V-VSPD + + true - - - - - - - - - - - - - - - ACCZ - - - - - - - GPS Time - - - GPS-T - - - - - - - - - - ACCX - - - - - - - - - - - - - - - - - - - GPS Lat/Lon - - - GPS-LL - - - - - - - VFAS - - - FAS-V - - - - - - - GPS Speed - - - GPS-S - - - - - - - CELLS - - - - - - - - - - - - - - - - ACCY - - - - - - - GPS Course - - - GPS-C - - - - - - - - - - - - - GPS Altitude - - - GPS-A - - - - - - - CURRENT - - - FAS-C - - - - - - - 30 - - - - - - - RSSI - - - - - - - 75 - - - - - - - SWR - - - - - - - FR_ID_AIR_SPEED - - - ASPD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A1 - - - - - - - A2 - - - - - - - A3 - - - - - - - A4 - - - - - - - Temperature 1 - - - T1 - - - - - - - Temperature 2 - - - T2 - - - - - - - RPM - - - - - - - FUEL + + false - - - When enabled, sends any non-blank values as simulated telemetry data. + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus - simulate + <| + + + false + + + true + + + false - + + + false + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Qt::StrongFocus + - Instance + > + + + true + + + true + + + false - - + + + + false + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Qt::StrongFocus + - 2 + <- + + + false + + + true + + + false + + + false + + + + + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + X + + + true + + + true + + + false - - - - - <html><head/><body><p>Start with <span style=" font-style:italic;">0x</span> for hex, <span style=" font-style:italic;">0</span> for octal, otherwise decimal.<br/>Numeric entry only. Blank values are not sent.</p></body></html> - - - - + + + + + 100 + 30 + 201 + 21 + + + + + 8 + + + + No Log File Currently Loaded + + + + + + 10 + 30 + 75 + 23 + + + + Load + + + false + + + + + false + + + + 410 + 50 + 201 + 16 + + + + Qt::Horizontal + + + + + false + + + + 150 + 70 + 161 + 22 + + + + 8 + + + 1 + + + 4 + + + Qt::Horizontal + + + + + + 140 + 90 + 181 + 20 + + + + + 8 + + + + Replay rate (1/5 -> 5x) + + + Qt::AlignCenter + + + + + + 410 + 70 + 191 + 31 + + + + + 8 + + + + Qt::LeftToRight + + + Row # +Timestamp + + + Qt::AlignCenter + + + + + + + 10 + 130 + 741 + 401 + + + + + + + + 8 + + + + RxBt + + + + + + + + 8 + + + + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + 5.200000000000000 + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + + + 8 + + + + V / ratio + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + VFAS + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Volts + + + + + + + + 8 + + + + RSSI + + + + + + + + 8 + + + + + + + + + 8 + + + + 75 + + + + + + + + 8 + + + + Db + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Curr + + + + + + + + 8 + + + + + + + + + 8 + + + + 1 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Amps + + + + + + + + 8 + + + + SWR + + + + + + + + 8 + + + + + + + + + 8 + + + + 30 + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Cels + + + + + + + + 8 + + + + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 8 + + + + 0.100000000000000 + + + + + + + + + + 8 + + + + Volts + + + + + + + + 8 + + + + A1 + + + + + + + + 8 + + + + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + + + 8 + + + + V / ratio + + + + + + + + 8 + + + + A2 + + + + + + + + 8 + + + + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + 2 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + + + 8 + + + + V / ratio + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + ASpd + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + KmH + + + + + + + + 8 + + + + A3 + + + + + + + + 8 + + + + + + + + + 8 + + + + 16777215 + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GAlt + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Meters + + + + + + + + 8 + + + + A4 + + + + + + + + 8 + + + + + + + + + 8 + + + + 16777215 + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GSpd + + + + + + + + 8 + + + + + + + + + 8 + + + + 1 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + KmH + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Tmp1 + + + + + + + + 8 + + + + + + + + + 8 + + + + -16777215 + + + 16777215 + + + 1 + + + + + + + + 8 + + + + Deg. C + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Hdg + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Degrees + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Tmp2 + + + + + + + + 8 + + + + + + + + + 8 + + + + -16777215 + + + 16777215 + + + + + + + + 8 + + + + Deg. C + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Date + + + + + + + + 8 + + + + + + + + + 8 + + + + + + + + + 8 + + + + dd-MM-yyyy hh:mm:ss + + + + + + + + 8 + + + + RPM + + + + + + + + 8 + + + + + + + + + 8 + + + + 65535 + + + + + + + + 8 + + + + RPM + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GPS + + + + + + + + 8 + + + + + + + + + 8 + + + + + + + + + 8 + + + + Lat,Lon (dec.deg.) + + + + + + + + 8 + + + + Fuel + + + + + + + + 8 + + + + + + + + + 8 + + + + 65535 + + + + + + + + 8 + + + + % + + + + + + + + 8 + + + + AccX + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + G + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + VSpd + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Meter/Sec. + + + + + + + + 8 + + + + AccY + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + G + + + + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Alt + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + Meters + + + + + + + + 8 + + + + AccZ + + + + + + + + 8 + + + + + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 8 + + + + G + + + + + Simulate + loadLogFile + replayRate + rewind + stepBack + play + stepForward + stop + rxbt_inst + rxbt + rxbt_ratio + rssi_inst Rssi + swr_inst Swr + a1_inst + A1 + A1_ratio + a2_inst + A2 + A2_ratio + a3_inst + A3 + a4_inst + A4 + t1_inst + T1 + t2_inst + T2 + rpm_inst + rpm + fuel_inst + fuel + vvspd_inst + vspeed + valt_inst + valt + fasv_inst + vfas + fasc_inst + curr + cells_inst + cell1 + cell2 + cell3 + cell4 + cell5 + cell6 + aspd_inst + aspeed + gpsa_inst + gps_alt + gpss_inst + gps_speed + gpsc_inst + gps_course + gpst_inst + gps_time + gpsll_inst + gps_latlon + accx_inst + accx + accy_inst + accy + accz_inst + accz