5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
22 #include "telemetrysimu.h"
23 #include "ui_telemetrysimu.h"
24 #include "simulatorinterface.h"
25 #include "radio/src/telemetry/frsky.h"
27 TelemetrySimulator::TelemetrySimulator(QWidget
* parent
, SimulatorInterface
* simulator
):
29 ui(new Ui::TelemetrySimulator
),
33 m_logReplayEnable(false)
37 ui
->A1
->setSpecialValueText(" ");
38 ui
->A2
->setSpecialValueText(" ");
39 ui
->A3
->setSpecialValueText(" ");
40 ui
->A4
->setSpecialValueText(" ");
41 ui
->rpm
->setSpecialValueText(" ");
42 ui
->fuel
->setSpecialValueText(" ");
44 ui
->rxbt_ratio
->setEnabled(false);
45 ui
->A1_ratio
->setEnabled(false);
46 ui
->A2_ratio
->setEnabled(false);
49 timer
.setInterval(10);
50 connect(&timer
, &QTimer::timeout
, this, &TelemetrySimulator::generateTelemetryFrame
);
52 connect(&logTimer
, &QTimer::timeout
, this, &TelemetrySimulator::onLogTimerEvent
);
54 logPlayback
= new LogPlaybackController(ui
);
56 connect(ui
->Simulate
, SIGNAL(toggled(bool)), this, SLOT(onSimulateToggled(bool)));
57 connect(ui
->loadLogFile
, SIGNAL(released()), this, SLOT(onLoadLogFile()));
58 connect(ui
->play
, SIGNAL(released()), this, SLOT(onPlay()));
59 connect(ui
->rewind
, SIGNAL(clicked()), this, SLOT(onRewind()));
60 connect(ui
->stepForward
, SIGNAL(clicked()), this, SLOT(onStepForward()));
61 connect(ui
->stepBack
, SIGNAL(clicked()), this, SLOT(onStepBack()));
62 connect(ui
->stop
, SIGNAL(clicked()), this, SLOT(onStop()));
63 connect(ui
->positionIndicator
, SIGNAL(valueChanged(int)), this, SLOT(onPositionIndicatorChanged(int)));
64 connect(ui
->replayRate
, SIGNAL(valueChanged(int)), this, SLOT(onReplayRateChanged(int)));
66 connect(this, &TelemetrySimulator::telemetryDataChanged
, simulator
, &SimulatorInterface::sendTelemetry
);
67 connect(simulator
, &SimulatorInterface::started
, this, &TelemetrySimulator::onSimulatorStarted
);
68 connect(simulator
, &SimulatorInterface::stopped
, this, &TelemetrySimulator::onSimulatorStopped
);
71 TelemetrySimulator::~TelemetrySimulator()
79 void TelemetrySimulator::onSimulatorStarted()
85 void TelemetrySimulator::onSimulatorStopped()
87 m_simuStarted
= false;
90 void TelemetrySimulator::onSimulateToggled(bool isChecked
)
100 void TelemetrySimulator::onLogTimerEvent()
102 logPlayback
->stepForward(false);
105 void TelemetrySimulator::onLoadLogFile()
107 onStop(); // in case we are in playback mode
108 logPlayback
->loadLogFile();
111 void TelemetrySimulator::onPlay()
113 if (logPlayback
->isReady()) {
114 logTimer
.start(logPlayback
->logFrequency
* 1000 / SPEEDS
[ui
->replayRate
->value()]);
119 void TelemetrySimulator::onRewind()
121 if (logPlayback
->isReady()) {
123 logPlayback
->rewind();
127 void TelemetrySimulator::onStepForward()
129 if (logPlayback
->isReady()) {
131 logPlayback
->stepForward(true);
135 void TelemetrySimulator::onStepBack()
137 if (logPlayback
->isReady()) {
139 logPlayback
->stepBack();
143 void TelemetrySimulator::onStop()
145 if (logPlayback
->isReady()) {
151 void TelemetrySimulator::onPositionIndicatorChanged(int value
)
153 if (logPlayback
->isReady()) {
154 logPlayback
->updatePositionLabel(value
);
155 logPlayback
->setUiDataValues();
159 void TelemetrySimulator::onReplayRateChanged(int value
)
161 if (logTimer
.isActive()) {
162 logTimer
.setInterval(logPlayback
->logFrequency
* 1000 / SPEEDS
[ui
->replayRate
->value()]);
166 void TelemetrySimulator::hideEvent(QHideEvent
*event
)
168 m_telemEnable
= ui
->Simulate
->isChecked();
169 m_logReplayEnable
= logTimer
.isActive();
171 ui
->Simulate
->setChecked(false);
176 void TelemetrySimulator::showEvent(QShowEvent
* event
)
178 ui
->Simulate
->setChecked(m_telemEnable
);
179 if (m_logReplayEnable
)
183 #define SET_INSTANCE(control, id, def) ui->control->setText(QString::number(simulator->getSensorInstance(id, ((def) & 0x1F) + 1)))
185 void TelemetrySimulator::setupDataFields()
187 SET_INSTANCE(rxbt_inst
, BATT_ID
, 0);
188 SET_INSTANCE(rssi_inst
, RSSI_ID
, 24);
189 SET_INSTANCE(swr_inst
, SWR_ID
, 24);
190 SET_INSTANCE(a1_inst
, ADC1_ID
, 0);
191 SET_INSTANCE(a2_inst
, ADC2_ID
, 0);
192 SET_INSTANCE(a3_inst
, A3_FIRST_ID
, 0);
193 SET_INSTANCE(a4_inst
, A4_FIRST_ID
, 0);
194 SET_INSTANCE(t1_inst
, T1_FIRST_ID
, 0);
195 SET_INSTANCE(t2_inst
, T2_FIRST_ID
, 0);
196 SET_INSTANCE(rpm_inst
, RPM_FIRST_ID
, DATA_ID_RPM
);
197 SET_INSTANCE(fuel_inst
, FUEL_FIRST_ID
, 0);
198 SET_INSTANCE(fuel_qty_inst
, FUEL_QTY_FIRST_ID
, 0);
199 SET_INSTANCE(aspd_inst
, AIR_SPEED_FIRST_ID
, 0);
200 SET_INSTANCE(vvspd_inst
, VARIO_FIRST_ID
, DATA_ID_VARIO
);
201 SET_INSTANCE(valt_inst
, ALT_FIRST_ID
, DATA_ID_VARIO
);
202 SET_INSTANCE(fasv_inst
, VFAS_FIRST_ID
, DATA_ID_FAS
);
203 SET_INSTANCE(fasc_inst
, CURR_FIRST_ID
, DATA_ID_FAS
);
204 SET_INSTANCE(cells_inst
, CELLS_FIRST_ID
, DATA_ID_FLVSS
);
205 SET_INSTANCE(gpsa_inst
, GPS_ALT_FIRST_ID
, DATA_ID_GPS
);
206 SET_INSTANCE(gpss_inst
, GPS_SPEED_FIRST_ID
, DATA_ID_GPS
);
207 SET_INSTANCE(gpsc_inst
, GPS_COURS_FIRST_ID
, DATA_ID_GPS
);
208 SET_INSTANCE(gpst_inst
, GPS_TIME_DATE_FIRST_ID
, DATA_ID_GPS
);
209 SET_INSTANCE(gpsll_inst
, GPS_LONG_LATI_FIRST_ID
, DATA_ID_GPS
);
210 SET_INSTANCE(accx_inst
, ACCX_FIRST_ID
, 0);
211 SET_INSTANCE(accy_inst
, ACCY_FIRST_ID
, 0);
212 SET_INSTANCE(accz_inst
, ACCZ_FIRST_ID
, 0);
214 refreshSensorRatios();
217 void setSportPacketCrc(uint8_t * packet
)
220 for (int i
=1; i
<FRSKY_SPORT_PACKET_SIZE
-1; i
++) {
221 crc
+= packet
[i
]; //0-1FF
222 crc
+= crc
>> 8; //0-100
224 crc
+= crc
>> 8; //0-0FF
227 packet
[FRSKY_SPORT_PACKET_SIZE
-1] = 0xFF - (crc
& 0x00ff);
230 uint8_t getBit(uint8_t position
, uint8_t value
)
232 return (value
& (uint8_t)(1 << position
)) ? 1 : 0;
235 bool generateSportPacket(uint8_t * packet
, uint8_t dataId
, uint8_t prim
, uint16_t appId
, uint32_t data
)
237 if (dataId
> 0x1B ) return false;
239 // generate Data ID field
240 uint8_t bit5
= getBit(0, dataId
) ^ getBit(1, dataId
) ^ getBit(2, dataId
);
241 uint8_t bit6
= getBit(2, dataId
) ^ getBit(3, dataId
) ^ getBit(4, dataId
);
242 uint8_t bit7
= getBit(0, dataId
) ^ getBit(2, dataId
) ^ getBit(4, dataId
);
244 packet
[0] = (bit7
<< 7) + (bit6
<< 6) + (bit5
<< 5) + dataId
;
245 // qDebug("dataID: 0x%02x (%d)", packet[0], dataId);
247 *((uint16_t *)(packet
+2)) = appId
;
248 *((int32_t *)(packet
+4)) = data
;
249 setSportPacketCrc(packet
);
253 void TelemetrySimulator::refreshSensorRatios()
255 ui
->rxbt_ratio
->setValue(simulator
->getSensorRatio(BATT_ID
) / 10.0);
256 ui
->A1_ratio
->setValue(simulator
->getSensorRatio(ADC1_ID
) / 10.0);
257 ui
->A2_ratio
->setValue(simulator
->getSensorRatio(ADC2_ID
) / 10.0);
260 void TelemetrySimulator::generateTelemetryFrame()
264 uint8_t buffer
[FRSKY_SPORT_PACKET_SIZE
] = {0};
265 static FlvssEmulator
*flvss
= new FlvssEmulator();
266 static GPSEmulator
*gps
= new GPSEmulator();
273 #if defined(XJT_VERSION_ID)
274 generateSportPacket(buffer
, 1, DATA_FRAME
, XJT_VERSION_ID
, 11);
276 refreshSensorRatios(); // placed here in order to call this less often
280 if (ui
->rxbt
->text().length()) {
281 generateSportPacket(buffer
, ui
->rxbt_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, BATT_ID
, LIMIT
<uint32_t>(0, ui
->rxbt
->value() * 255.0 / ui
->rxbt_ratio
->value(), 0xFFFFFFFF));
286 if (ui
->Rssi
->text().length())
287 generateSportPacket(buffer
, ui
->rssi_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, RSSI_ID
, LIMIT
<uint32_t>(0, ui
->Rssi
->text().toInt(&ok
, 0), 0xFF));
291 if (ui
->Swr
->text().length())
292 generateSportPacket(buffer
, ui
->swr_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, SWR_ID
, LIMIT
<uint32_t>(0, ui
->Swr
->text().toInt(&ok
, 0), 0xFFFF));
296 if (ui
->A1
->value() > 0)
297 generateSportPacket(buffer
, ui
->a1_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ADC1_ID
, LIMIT
<uint32_t>(0, ui
->A1
->value() * 255.0 / ui
->A1_ratio
->value(), 0xFF));
301 if (ui
->A2
->value() > 0)
302 generateSportPacket(buffer
, ui
->a2_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ADC2_ID
, LIMIT
<uint32_t>(0, ui
->A2
->value() * 255.0 / ui
->A2_ratio
->value(), 0xFF));
306 if (ui
->A3
->value() > 0)
307 generateSportPacket(buffer
, ui
->a3_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, A3_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->A3
->value() * 100.0, 0xFFFFFFFF));
311 if (ui
->A4
->value() > 0)
312 generateSportPacket(buffer
, ui
->a4_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, A4_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->A4
->value() * 100.0, 0xFFFFFFFF));
316 if (ui
->T1
->value() != 0)
317 generateSportPacket(buffer
, ui
->t1_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, T1_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->T1
->value(), 0x7FFFFFFF));
321 if (ui
->T2
->value() != 0)
322 generateSportPacket(buffer
, ui
->t2_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, T2_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->T2
->value(), 0x7FFFFFFF));
326 if (ui
->rpm
->value() > 0)
327 generateSportPacket(buffer
, ui
->rpm_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, RPM_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->rpm
->value(), 0x7FFFFFFF));
331 if (ui
->fuel
->value() > 0)
332 generateSportPacket(buffer
, ui
->fuel_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, FUEL_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->fuel
->value(), 0xFFFF));
336 if (ui
->vspeed
->value() != 0)
337 generateSportPacket(buffer
, ui
->vvspd_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, VARIO_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->vspeed
->value() * 100.0, 0x7FFFFFFF));
341 if (ui
->valt
->value() != 0)
342 generateSportPacket(buffer
, ui
->valt_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ALT_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->valt
->value() * 100.0, 0x7FFFFFFF));
346 if (ui
->vfas
->value() != 0)
347 generateSportPacket(buffer
, ui
->fasv_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, VFAS_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->vfas
->value() * 100.0, 0xFFFFFFFF));
351 if (ui
->curr
->value() != 0)
352 generateSportPacket(buffer
, ui
->fasc_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, CURR_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->curr
->value() * 10.0, 0xFFFFFFFF));
356 double cellValues
[FlvssEmulator::MAXCELLS
];
357 if (ui
->cell1
->value() > 0.009) { // ??? cell1 returning non-zero value when spin box is zero!
358 cellValues
[0] = ui
->cell1
->value();
359 cellValues
[1] = ui
->cell2
->value();
360 cellValues
[2] = ui
->cell3
->value();
361 cellValues
[3] = ui
->cell4
->value();
362 cellValues
[4] = ui
->cell5
->value();
363 cellValues
[5] = ui
->cell6
->value();
364 generateSportPacket(buffer
, ui
->cells_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, CELLS_FIRST_ID
, flvss
->setAllCells_GetNextPair(cellValues
));
368 flvss
->setAllCells_GetNextPair(cellValues
);
373 if (ui
->aspeed
->value() > 0)
374 generateSportPacket(buffer
, ui
->aspd_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, AIR_SPEED_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->aspeed
->value() * 5.39957, 0xFFFFFFFF));
378 if (ui
->gps_alt
->value() != 0) {
379 gps
->setGPSAltitude(ui
->gps_alt
->value());
380 generateSportPacket(buffer
, ui
->gpsa_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, GPS_ALT_FIRST_ID
, gps
->getNextPacketData(GPS_ALT_FIRST_ID
));
385 if (ui
->gps_speed
->value() > 0) {
386 gps
->setGPSSpeedKMH(ui
->gps_speed
->value());
387 generateSportPacket(buffer
, ui
->gpss_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, GPS_SPEED_FIRST_ID
, gps
->getNextPacketData(GPS_SPEED_FIRST_ID
));
392 if (ui
->gps_course
->value() != 0) {
393 gps
->setGPSCourse(ui
->gps_course
->value());
394 generateSportPacket(buffer
, ui
->gpsc_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, GPS_COURS_FIRST_ID
, gps
->getNextPacketData(GPS_COURS_FIRST_ID
));
399 if (ui
->gps_time
->text().length()) {
400 gps
->setGPSDateTime(ui
->gps_time
->text());
401 generateSportPacket(buffer
, ui
->gpst_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, GPS_TIME_DATE_FIRST_ID
, gps
->getNextPacketData(GPS_TIME_DATE_FIRST_ID
));
406 if (ui
->gps_latlon
->text().length()) {
407 gps
->setGPSLatLon(ui
->gps_latlon
->text());
408 generateSportPacket(buffer
, ui
->gpsll_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, GPS_LONG_LATI_FIRST_ID
, gps
->getNextPacketData(GPS_LONG_LATI_FIRST_ID
));
413 if (ui
->accx
->value() != 0)
414 generateSportPacket(buffer
, ui
->accx_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ACCX_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->accx
->value() * 100.0, 0x7FFFFFFF));
418 if (ui
->accy
->value() != 0)
419 generateSportPacket(buffer
, ui
->accy_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ACCY_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->accy
->value() * 100.0, 0x7FFFFFFF));
423 if (ui
->accz
->value() != 0)
424 generateSportPacket(buffer
, ui
->accz_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, ACCZ_FIRST_ID
, LIMIT
<int32_t>(-0x7FFFFFFF, ui
->accz
->value() * 100.0, 0x7FFFFFFF));
428 if (ui
->fuel_qty
->value() > 0)
429 generateSportPacket(buffer
, ui
->fuel_qty_inst
->text().toInt(&ok
, 0) - 1, DATA_FRAME
, FUEL_QTY_FIRST_ID
, LIMIT
<uint32_t>(0, ui
->fuel_qty
->value() * 100.0, 0xFFFFFF));
437 if (ok
&& (buffer
[2] || buffer
[3])) {
438 QByteArray
ba((char *)buffer
, FRSKY_SPORT_PACKET_SIZE
);
439 emit
telemetryDataChanged(ba
);
440 //qDebug("%02X %02X %02X %02X %02X %02X %02X %02X %02X", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8]);
443 generateTelemetryFrame();
447 uint32_t TelemetrySimulator::FlvssEmulator::encodeCellPair(uint8_t cellNum
, uint8_t firstCellNo
, double cell1
, double cell2
)
449 uint16_t cell1Data
= cell1
* 500.0;
450 uint16_t cell2Data
= cell2
* 500.0;
451 uint32_t cellData
= 0;
453 cellData
= cell2Data
& 0x0FFF;
455 cellData
|= cell1Data
& 0x0FFF;
457 cellData
|= cellNum
& 0x0F;
459 cellData
|= firstCellNo
& 0x0F;
464 void TelemetrySimulator::FlvssEmulator::encodeAllCells()
466 cellData1
= encodeCellPair(numCells
, 0, cellFloats
[0], cellFloats
[1]);
467 if (numCells
> 2) cellData2
= encodeCellPair(numCells
, 2, cellFloats
[2], cellFloats
[3]); else cellData2
= 0;
468 if (numCells
> 4) cellData3
= encodeCellPair(numCells
, 4, cellFloats
[4], cellFloats
[5]); else cellData3
= 0;
471 void TelemetrySimulator::FlvssEmulator::splitIntoCells(double totalVolts
)
473 numCells
= qFloor((totalVolts
/ 3.7) + .5);
474 double avgVolts
= totalVolts
/ numCells
;
475 double remainder
= (totalVolts
- (avgVolts
* numCells
));
476 for (uint32_t i
= 0; (i
< numCells
) && ( i
< MAXCELLS
); i
++) {
477 cellFloats
[i
] = avgVolts
;
479 for (uint32_t i
= numCells
; i
< MAXCELLS
; i
++) {
482 cellFloats
[0] += remainder
;
483 numCells
= numCells
> MAXCELLS
? MAXCELLS
: numCells
; // force into valid cell count in case of input out of range
486 uint32_t TelemetrySimulator::FlvssEmulator::setAllCells_GetNextPair(double cellValues
[6])
489 for (uint32_t i
= 0; i
< MAXCELLS
; i
++) {
490 if ((i
== 0) && (cellValues
[0] > 4.2)) {
491 splitIntoCells(cellValues
[0]);
494 if (cellValues
[i
] > 0) {
495 cellFloats
[i
] = cellValues
[i
];
499 // zero marks the last cell
500 for (uint32_t x
= i
; x
< MAXCELLS
; x
++) {
507 // encode the double values into telemetry format
510 // return the value for the current pair
511 uint32_t cellData
= 0;
512 if (nextCellNum
>= numCells
) {
515 switch (nextCellNum
) {
517 cellData
= cellData1
;
520 cellData
= cellData2
;
523 cellData
= cellData3
;
530 TelemetrySimulator::GPSEmulator::GPSEmulator()
534 dt
= QDateTime::currentDateTime();
540 uint32_t TelemetrySimulator::GPSEmulator::encodeLatLon(double latLon
, bool isLat
)
542 uint32_t data
= (uint32_t)((latLon
< 0 ? -latLon
: latLon
) * 60 * 10000) & 0x3FFFFFFF;
543 if (isLat
== false) {
552 uint32_t TelemetrySimulator::GPSEmulator::encodeDateTime(uint8_t yearOrHour
, uint8_t monthOrMinute
, uint8_t dayOrSecond
, bool isDate
)
554 uint32_t data
= yearOrHour
;
556 data
|= monthOrMinute
;
560 if (isDate
== true) {
566 uint32_t TelemetrySimulator::GPSEmulator::getNextPacketData(uint32_t packetType
)
568 switch (packetType
) {
569 case GPS_LONG_LATI_FIRST_ID
:
571 return sendLat
? encodeLatLon(lat
, true) : encodeLatLon(lon
, false);
573 case GPS_TIME_DATE_FIRST_ID
:
574 sendDate
= !sendDate
;
575 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);
577 case GPS_ALT_FIRST_ID
:
578 return (uint32_t) (altitude
* 100);
580 case GPS_SPEED_FIRST_ID
:
581 return speedKNTS
* 1000;
583 case GPS_COURS_FIRST_ID
:
590 void TelemetrySimulator::GPSEmulator::setGPSDateTime(QString dateTime
)
592 dt
= QDateTime::currentDateTime().toTimeSpec(Qt::UTC
); // default to current systemtime
593 if (!dateTime
.startsWith('*')) {
594 QString
format("dd-MM-yyyy hh:mm:ss");
595 dt
= QDateTime::fromString(dateTime
, format
);
599 void TelemetrySimulator::GPSEmulator::setGPSLatLon(QString latLon
)
601 QStringList coords
= latLon
.split(",");
604 if (coords
.length() > 1)
606 lat
= coords
[0].toDouble();
607 lon
= coords
[1].toDouble();
611 void TelemetrySimulator::GPSEmulator::setGPSCourse(double course
)
613 this->course
= course
;
616 void TelemetrySimulator::GPSEmulator::setGPSSpeedKMH(double speedKMH
)
618 this->speedKNTS
= speedKMH
* 0.539957;
621 void TelemetrySimulator::GPSEmulator::setGPSAltitude(double altitude
)
623 this->altitude
= altitude
;
626 TelemetrySimulator::LogPlaybackController::LogPlaybackController(Ui::TelemetrySimulator
* ui
)
628 TelemetrySimulator::LogPlaybackController::ui
= ui
;
630 // initialize the map - TODO: how should this be localized?
631 colToFuncMap
.clear();
632 colToFuncMap
.insert("RxBt(V)", RXBT_V
);
633 colToFuncMap
.insert("RSSI(dB)", RSSI
);
634 colToFuncMap
.insert("SWR", SWR
);
635 colToFuncMap
.insert("A1", A1
);
636 colToFuncMap
.insert("A1(V)", A1
);
637 colToFuncMap
.insert("A2", A2
);
638 colToFuncMap
.insert("A2(V)", A2
);
639 colToFuncMap
.insert("A3", A3
);
640 colToFuncMap
.insert("A3(V)", A3
);
641 colToFuncMap
.insert("A4", A4
);
642 colToFuncMap
.insert("A4(V)", A4
);
643 colToFuncMap
.insert("Tmp1(@C)", T1_DEGC
);
644 colToFuncMap
.insert("Tmp1(@F)", T1_DEGF
);
645 colToFuncMap
.insert("Tmp2(@C)", T2_DEGC
);
646 colToFuncMap
.insert("Tmp2(@F)", T2_DEGF
);
647 colToFuncMap
.insert("RPM(rpm)", RPM
);
648 colToFuncMap
.insert("Fuel(%)", FUEL
);
649 colToFuncMap
.insert("Fuel(ml)", FUEL_QTY
);
650 colToFuncMap
.insert("VSpd(m/s)", VSPD_MS
);
651 colToFuncMap
.insert("VSpd(f/s)", VSPD_FS
);
652 colToFuncMap
.insert("Alt(ft)", ALT_FEET
);
653 colToFuncMap
.insert("Alt(m)", ALT_METERS
);
654 colToFuncMap
.insert("VFAS(V)", FASV
);
655 colToFuncMap
.insert("Curr(A)", FASC
);
656 colToFuncMap
.insert("Cels(gRe)", CELS_GRE
);
657 colToFuncMap
.insert("ASpd(kts)", ASPD_KTS
);
658 colToFuncMap
.insert("ASpd(kmh)", ASPD_KMH
);
659 colToFuncMap
.insert("ASpd(mph)", ASPD_MPH
);
660 colToFuncMap
.insert("GAlt(ft)", GALT_FEET
);
661 colToFuncMap
.insert("GAlt(m)", GALT_METERS
);
662 colToFuncMap
.insert("GSpd(kts)", GSPD_KNTS
);
663 colToFuncMap
.insert("GSpd(kmh)", GSPD_KMH
);
664 colToFuncMap
.insert("GSpd(mph)", GSPD_MPH
);
665 colToFuncMap
.insert("Hdg(@)", GHDG_DEG
);
666 colToFuncMap
.insert("Date", GDATE
);
667 colToFuncMap
.insert("GPS", G_LATLON
);
668 colToFuncMap
.insert("AccX(g)", ACCX
);
669 colToFuncMap
.insert("AccY(g)", ACCY
);
670 colToFuncMap
.insert("AccZ(g)", ACCZ
);
675 QDateTime
TelemetrySimulator::LogPlaybackController::parseTransmittterTimestamp(QString row
)
677 QStringList rowParts
= row
.simplified().split(',');
678 if (rowParts
.size() < 2) {
681 QString datePart
= rowParts
[0];
682 QString timePart
= rowParts
[1];
684 QString
format("yyyy-MM-dd hh:mm:ss.zzz"); // assume this format
685 // hour can be 'missing'
686 if (timePart
.count(":") < 2) {
687 timePart
= "00:" + timePart
;
689 if (datePart
.contains("/")) { // happens when csv is edited by Excel
690 format
= "M/d/yyyy hh:mm:ss.z";
692 return QDateTime::fromString(datePart
+ " " + timePart
, format
);
695 void TelemetrySimulator::LogPlaybackController::calcLogFrequency()
697 // examine up to 20 rows to determine log frequency in seconds
698 // Skip the first entry which contains the file open time
699 logFrequency
= 25.5; // default value
701 for (int i
= 2; (i
< 21) && (i
< csvRecords
.count()); i
++)
703 QDateTime logTime
= parseTransmittterTimestamp(csvRecords
[i
]);
704 // ugh - no timespan in this Qt version
705 double timeDiff
= (logTime
.toMSecsSinceEpoch() - lastTime
.toMSecsSinceEpoch()) / 1000.0;
706 if ((timeDiff
> 0.09) && (timeDiff
< logFrequency
)) {
707 logFrequency
= timeDiff
;
713 bool TelemetrySimulator::LogPlaybackController::isReady()
715 return csvRecords
.count() > 1;
718 void TelemetrySimulator::LogPlaybackController::loadLogFile()
720 // reset the playback ui
721 ui
->play
->setEnabled(false);
722 ui
->rewind
->setEnabled(false);
723 ui
->stepBack
->setEnabled(false);
724 ui
->stepForward
->setEnabled(false);
725 ui
->stop
->setEnabled(false);
726 ui
->positionIndicator
->setEnabled(false);
727 ui
->replayRate
->setEnabled(false);
728 ui
->positionLabel
->setText("Row #\nTimestamp");
730 // clear existing data
733 QString logFileNameAndPath
= QFileDialog::getOpenFileName(NULL
, tr("Log File"), ".", tr("LOG Files (*.csv)"));
734 QFileInfo
fileInfo(logFileNameAndPath
);
735 QFile
file(logFileNameAndPath
);
736 if (!file
.open(QIODevice::ReadOnly
)) {
737 ui
->logFileLabel
->setText(tr("ERROR - invalid file"));
740 while (!file
.atEnd()) {
741 QByteArray line
= file
.readLine();
742 csvRecords
.append(line
.simplified());
744 if (csvRecords
.count() > 1) {
746 QStringList keys
= csvRecords
[0].split(',');
747 // override the first two column names
750 Q_FOREACH(QString key
, keys
) {
751 columnNames
.append(key
.simplified());
753 ui
->play
->setEnabled(true);
754 ui
->rewind
->setEnabled(true);
755 ui
->stepBack
->setEnabled(true);
756 ui
->stepForward
->setEnabled(true);
757 ui
->stop
->setEnabled(true);
758 ui
->positionIndicator
->setEnabled(true);
759 ui
->replayRate
->setEnabled(true);
760 supportedCols
.clear();
764 ui
->logFileLabel
->setText(QFileInfo(logFileNameAndPath
).fileName());
765 // iterate through all known mappings and add those that are used
766 QMapIterator
<QString
, CONVERT_TYPE
> it(colToFuncMap
);
767 while (it
.hasNext()) {
769 addColumnHash(it
.key(), it
.value());
775 void TelemetrySimulator::LogPlaybackController::addColumnHash(QString key
, CONVERT_TYPE functionIndex
)
777 DATA_TO_FUNC_XREF dfx
;
778 if (columnNames
.contains(key
)) {
779 dfx
.functionIndex
= functionIndex
;
780 dfx
.dataIndex
= columnNames
.indexOf(key
);
781 supportedCols
.append(dfx
);
785 void TelemetrySimulator::LogPlaybackController::play()
789 void TelemetrySimulator::LogPlaybackController::stop()
793 void TelemetrySimulator::LogPlaybackController::rewind()
797 ui
->stop
->setChecked(true);
798 updatePositionLabel(-1);
803 void TelemetrySimulator::LogPlaybackController::stepForward(bool focusOnStop
)
806 if (recordIndex
< (csvRecords
.count() - 1)) {
809 ui
->stop
->setChecked(true);
811 updatePositionLabel(-1);
815 rewind(); // always loop at the end
820 void TelemetrySimulator::LogPlaybackController::stepBack()
823 if (recordIndex
> 1) {
825 ui
->stop
->setChecked(true);
826 updatePositionLabel(-1);
832 double TelemetrySimulator::LogPlaybackController::convertFeetToMeters(QString input
)
834 double meters100
= input
.toDouble() * 0.3048;
835 return qFloor(meters100
+ .005);
838 QString
TelemetrySimulator::LogPlaybackController::convertGPSDate(QString input
)
840 QStringList dateTime
= input
.simplified().split(' ');
841 if (dateTime
.size() < 2) {
842 return ""; // invalid format
844 QStringList dateParts
= dateTime
[0].split('-'); // input as yy-mm-dd
845 if (dateParts
.size() < 3) {
846 return ""; // invalid format
848 // output is dd-MM-yyyy hh:mm:ss
849 QString localDateString
= dateParts
[2] + "-" + dateParts
[1] + "-20" + dateParts
[0] + " " + dateTime
[1];
850 QString
format("dd-MM-yyyy hh:mm:ss");
851 QDateTime utcDate
= QDateTime::fromString(localDateString
, format
).toTimeSpec(Qt::UTC
);
852 return utcDate
.toString(format
);
855 double TelemetrySimulator::LogPlaybackController::convertDegMin(QString input
)
857 double fInput
= input
.mid(0, input
.length() - 1).toDouble();
858 double degrees
= qFloor(fInput
/ 100.0);
859 double minutes
= fInput
- (degrees
* 100);
860 int32_t sign
= ((input
.endsWith('E')) || (input
.endsWith('N'))) ? 1 : -1;
861 return (degrees
+ (minutes
/ 60)) * sign
;
864 QString
TelemetrySimulator::LogPlaybackController::convertGPS(QString input
)
866 // input format is DDmm.mmmmH DDDmm.mmmmH (longitude latitude - degrees (2 places) minutes (2 places) decimal minutes (4 places))
867 QStringList lonLat
= input
.simplified().split(' ');
868 if (lonLat
.count() < 2) {
869 return ""; // invalid format
871 double lon
= convertDegMin(lonLat
[0]);
872 double lat
= convertDegMin(lonLat
[1]);
873 return QString::number(lat
) + ", " + QString::number(lon
);
876 void TelemetrySimulator::LogPlaybackController::updatePositionLabel(int32_t percentage
)
878 if ((percentage
> 0) && (!stepping
)) {
879 recordIndex
= qFloor((double)csvRecords
.count() / 100.0 * percentage
);
880 if (recordIndex
== 0) {
881 recordIndex
= 1; // record 0 is column labels
884 // format the transmitter date info
885 QDateTime transmitterTimestamp
= parseTransmittterTimestamp(csvRecords
[recordIndex
]);
886 QString
format("yyyy-MM-dd hh:mm:ss.z");
887 ui
->positionLabel
->setText("Row " + QString::number(recordIndex
) + " of " + QString::number(csvRecords
.count() - 1)
888 + "\n" + transmitterTimestamp
.toString(format
));
889 if (percentage
< 0) { // did we step past a threshold?
890 uint32_t posPercent
= (recordIndex
/ (double)(csvRecords
.count() - 1)) * 100;
891 ui
->positionIndicator
->setValue(posPercent
);
895 double TelemetrySimulator::LogPlaybackController::convertFahrenheitToCelsius(QString input
)
897 return (input
.toDouble() - 32.0) * 0.5556;
900 void TelemetrySimulator::LogPlaybackController::setUiDataValues()
902 QStringList columnData
= csvRecords
[recordIndex
].split(',');
903 Q_FOREACH(DATA_TO_FUNC_XREF info
, supportedCols
) {
904 if (info
.dataIndex
< columnData
.size()) {
905 switch (info
.functionIndex
) {
907 ui
->rxbt
->setValue(columnData
[info
.dataIndex
].toDouble());
910 ui
->Rssi
->setValue(columnData
[info
.dataIndex
].toDouble());
913 ui
->Swr
->setValue(columnData
[info
.dataIndex
].toDouble());
916 ui
->A1
->setValue(columnData
[info
.dataIndex
].toDouble());
919 ui
->A2
->setValue(columnData
[info
.dataIndex
].toDouble());
922 ui
->A3
->setValue(columnData
[info
.dataIndex
].toDouble());
925 ui
->A4
->setValue(columnData
[info
.dataIndex
].toDouble());
928 ui
->T1
->setValue(columnData
[info
.dataIndex
].toDouble());
931 ui
->T2
->setValue(columnData
[info
.dataIndex
].toDouble());
934 ui
->T1
->setValue(convertFahrenheitToCelsius(columnData
[info
.dataIndex
]));
937 ui
->T2
->setValue(convertFahrenheitToCelsius(columnData
[info
.dataIndex
]));
940 ui
->rpm
->setValue(columnData
[info
.dataIndex
].toDouble());
943 ui
->fuel
->setValue(columnData
[info
.dataIndex
].toDouble());
946 ui
->fuel_qty
->setValue(columnData
[info
.dataIndex
].toDouble());
949 ui
->vspeed
->setValue(columnData
[info
.dataIndex
].toDouble());
952 ui
->vspeed
->setValue(columnData
[info
.dataIndex
].toDouble() * 0.3048);
955 ui
->valt
->setValue(convertFeetToMeters(columnData
[info
.dataIndex
]));
958 ui
->valt
->setValue(columnData
[info
.dataIndex
].toDouble());
961 ui
->vfas
->setValue(columnData
[info
.dataIndex
].toDouble());
964 ui
->curr
->setValue(columnData
[info
.dataIndex
].toDouble());
967 ui
->cell1
->setValue(columnData
[info
.dataIndex
].toDouble());
970 ui
->aspeed
->setValue(columnData
[info
.dataIndex
].toDouble() * 1.8520008892119);
973 ui
->aspeed
->setValue(columnData
[info
.dataIndex
].toDouble());
976 ui
->aspeed
->setValue(columnData
[info
.dataIndex
].toDouble() * 1.60934);
979 ui
->gps_alt
->setValue(convertFeetToMeters(columnData
[info
.dataIndex
]));
982 ui
->gps_alt
->setValue(columnData
[info
.dataIndex
].toDouble());
985 ui
->gps_speed
->setValue(columnData
[info
.dataIndex
].toDouble() * 1.852);
988 ui
->gps_speed
->setValue(columnData
[info
.dataIndex
].toDouble());
991 ui
->gps_speed
->setValue(columnData
[info
.dataIndex
].toDouble() * 1.60934);
994 ui
->gps_course
->setValue(columnData
[info
.dataIndex
].toDouble());
997 ui
->gps_time
->setText(convertGPSDate(columnData
[info
.dataIndex
]));
1000 ui
->gps_latlon
->setText(convertGPS(columnData
[info
.dataIndex
]));
1003 ui
->accx
->setValue(columnData
[info
.dataIndex
].toDouble());
1006 ui
->accy
->setValue(columnData
[info
.dataIndex
].toDouble());
1009 ui
->accz
->setValue(columnData
[info
.dataIndex
].toDouble());
1014 // file is corrupt - shut down with open logs, or log format changed mid-day