2 * Author - Bertrand Songis <bsongis@gmail.com>
4 * Based on th9x -> http://code.google.com/p/th9x/
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
17 #include "opentxsimulator.h"
22 #include <QElapsedTimer>
24 #if !defined(MAX_LOGICAL_SWITCHES) && defined(NUM_CSW)
25 #define MAX_LOGICAL_SWITCHES NUM_CSW
28 #define GET_SWITCH_BOOL(sw__) getSwitch((sw__), 0);
30 #define OTXS_DBG qDebug() << "(" << simuTimerMicros() << "us)"
32 int16_t g_anas
[Analogs::NUM_ANALOGS
];
33 QVector
<QIODevice
*> OpenTxSimulator::tracebackDevices
;
35 uint16_t anaIn(uint8_t chan
)
40 uint16_t getAnalogValue(uint8_t index
)
45 void firmwareTraceCb(const char * text
)
47 foreach (QIODevice
* dev
, OpenTxSimulator::tracebackDevices
) {
53 OpenTxSimulator::OpenTxSimulator() :
56 m_resetOutputsData(true),
57 m_stopRequested(false)
59 tracebackDevices
.clear();
60 traceCallback
= firmwareTraceCb
;
63 OpenTxSimulator::~OpenTxSimulator()
65 traceCallback
= nullptr;
66 tracebackDevices
.clear();
75 while (isRunning() && !tmout
.hasExpired(1000))
78 //qDebug() << "Deleting OpenTxSimulator";
81 QString
OpenTxSimulator::name()
83 return QString(SIMULATOR_FLAVOUR
);
86 bool OpenTxSimulator::isRunning()
88 QMutexLocker
lckr(&m_mtxSimuMain
);
89 return simuIsRunning();
92 void OpenTxSimulator::init()
100 // make sure we create & control the timer from current thread
101 m_timer10ms
= new QTimer();
102 m_timer10ms
->setInterval(10);
103 connect(m_timer10ms
, &QTimer::timeout
, this, &OpenTxSimulator::run
);
104 connect(this, SIGNAL(started()), m_timer10ms
, SLOT(start()));
105 connect(this, SIGNAL(stopped()), m_timer10ms
, SLOT(stop()));
108 m_resetOutputsData
= true;
109 setStopRequested(false);
111 QMutexLocker
lckr(&m_mtxSimuMain
);
112 memset(g_anas
, 0, sizeof(g_anas
));
114 #if defined(PCBTARANIS)
115 g_anas
[TX_RTC_VOLTAGE
] = 800; // 2,34V
121 void OpenTxSimulator::start(const char * filename
, bool tests
)
125 OTXS_DBG
<< "file:" << filename
<< "tests:" << tests
;
127 QMutexLocker
lckr(&m_mtxSimuMain
);
128 QMutexLocker
slckr(&m_mtxSettings
);
129 StartEepromThread(filename
);
130 StartAudioThread(volumeGain
);
131 StartSimu(tests
, simuSdDirectory
.toLatin1().constData(), simuSettingsDirectory
.toLatin1().constData());
134 QTimer::singleShot(0, this, SLOT(run())); // old style for Qt < 5.4
137 void OpenTxSimulator::stop()
143 setStopRequested(true);
145 QMutexLocker
lckr(&m_mtxSimuMain
);
153 void OpenTxSimulator::setSdPath(const QString
& sdPath
, const QString
& settingsPath
)
155 QMutexLocker
lckr(&m_mtxSettings
);
156 simuSdDirectory
= sdPath
;
157 simuSettingsDirectory
= settingsPath
;
160 void OpenTxSimulator::setVolumeGain(const int value
)
162 QMutexLocker
lckr(&m_mtxSettings
);
166 void OpenTxSimulator::setRadioData(const QByteArray
& data
)
168 #if defined(EEPROM_SIZE)
169 QMutexLocker
lckr(&m_mtxRadioData
);
170 eeprom
= (uint8_t *)malloc(qMin
<int>(EEPROM_SIZE
, data
.size()));
171 memcpy(eeprom
, data
.data(), qMin
<int>(EEPROM_SIZE
, data
.size()));
175 void OpenTxSimulator::readRadioData(QByteArray
& dest
)
177 #if defined(EEPROM_SIZE)
178 QMutexLocker
lckr(&m_mtxRadioData
);
179 memcpy(dest
.data(), eeprom
, std::min
<int>(EEPROM_SIZE
, dest
.size()));
183 uint8_t * OpenTxSimulator::getLcd()
185 return (uint8_t *)simuLcdBuf
;
188 void OpenTxSimulator::setAnalogValue(uint8_t index
, int16_t value
)
190 static int dim
= DIM(g_anas
);
192 g_anas
[index
] = value
;
195 void OpenTxSimulator::setSwitch(uint8_t swtch
, int8_t state
)
197 simuSetSwitch(swtch
, state
);
200 void OpenTxSimulator::setKey(uint8_t key
, bool state
)
202 simuSetKey(key
, state
);
205 void OpenTxSimulator::setTrimSwitch(uint8_t trim
, bool state
)
207 simuSetTrim(trim
, state
);
210 void OpenTxSimulator::setTrim(unsigned int idx
, int value
)
213 if (i
< 4) // swap axes
214 i
= modn12x3
[4 * getStickMode() + idx
];
215 uint8_t phase
= getTrimFlightMode(getFlightMode(), i
);
217 if (!setTrimValue(phase
, i
, value
)) {
218 QTimer
*timer
= new QTimer(this);
219 timer
->setSingleShot(true);
220 connect(timer
, &QTimer::timeout
, [=]() {
221 emit
trimValueChange(idx
, 0);
222 emit
outputValueChange(OUTPUT_SRC_TRIM_VALUE
, idx
, 0);
223 timer
->deleteLater();
229 void OpenTxSimulator::setTrainerInput(unsigned int inputNumber
, int16_t value
)
231 static unsigned dim
= DIM(ppmInput
);
232 //setTrainerTimeout(100);
233 if (inputNumber
< dim
)
234 ppmInput
[inputNumber
] = qMin(qMax((int16_t)-512, value
), (int16_t)512);
237 void OpenTxSimulator::setInputValue(int type
, uint8_t index
, int16_t value
)
239 //qDebug() << type << index << value << this->thread();
241 case INPUT_SRC_ANALOG
:
242 case INPUT_SRC_STICK
:
243 setAnalogValue(index
, value
);
245 case INPUT_SRC_KNOB
:
246 setAnalogValue(index
+ NUM_STICKS
, value
);
248 case INPUT_SRC_SLIDER
:
249 setAnalogValue(index
+ NUM_STICKS
+ NUM_POTS
, value
);
251 case INPUT_SRC_TXVIN
:
252 setAnalogValue(Analogs::TX_VOLTAGE
, voltageToAdc(value
));
254 case INPUT_SRC_SWITCH
:
255 setSwitch(index
, (int8_t)value
);
257 case INPUT_SRC_TRIM_SW
:
258 setTrimSwitch(index
, (bool)value
);
260 case INPUT_SRC_TRIM
:
261 setTrim(index
, value
);
264 setKey(index
, (bool)value
);
266 case INPUT_SRC_TRAINER
:
267 setTrainerInput(index
, value
);
269 case INPUT_SRC_ROTENC
: // TODO
275 void OpenTxSimulator::rotaryEncoderEvent(int steps
)
277 #if defined(ROTARY_ENCODER_NAVIGATION)
278 ROTARY_ENCODER_NAVIGATION_VALUE
+= steps
* ROTARY_ENCODER_GRANULARITY
;
280 // TODO : this should probably be handled in the GUI
282 #if defined(PCBXLITE)
294 // Should not happen but Clang complains that key is unset otherwise
298 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
299 QTimer::singleShot(10, [this, key
]() { setKey(key
, 0); });
301 QTimer
*timer
= new QTimer(this);
302 timer
->setSingleShot(true);
303 connect(timer
, &QTimer::timeout
, [=]() {
305 timer
->deleteLater();
309 #endif // defined(ROTARY_ENCODER_NAVIGATION)
312 void OpenTxSimulator::setTrainerTimeout(uint16_t ms
)
314 ppmInputValidityTimer
= ms
;
317 void OpenTxSimulator::sendTelemetry(const QByteArray data
)
320 sportProcessTelemetryPacket((uint8_t *)data
.constData());
323 uint8_t OpenTxSimulator::getSensorInstance(uint16_t id
, uint8_t defaultValue
)
325 for (int i
= 0; i
< MAX_TELEMETRY_SENSORS
; i
++) {
326 if (isTelemetryFieldAvailable(i
)) {
327 TelemetrySensor
* sensor
= &g_model
.telemetrySensors
[i
];
328 if (sensor
->id
== id
) {
329 return sensor
->instance
;
336 uint16_t OpenTxSimulator::getSensorRatio(uint16_t id
)
338 for (int i
= 0; i
< MAX_TELEMETRY_SENSORS
; i
++) {
339 if (isTelemetryFieldAvailable(i
)) {
340 TelemetrySensor
* sensor
= &g_model
.telemetrySensors
[i
];
341 if (sensor
->id
== id
) {
342 return sensor
->custom
.ratio
;
349 const int OpenTxSimulator::getCapability(Capability cap
)
359 case CAP_ROTARY_ENC
:
362 case CAP_ROTARY_ENC_NAV
:
363 #ifdef ROTARY_ENCODER_NAVIGATION
368 case CAP_TELEM_FRSKY_SPORT
:
375 void OpenTxSimulator::setLuaStateReloadPermanentScripts()
378 luaState
= INTERPRETER_RELOAD_PERMANENT_SCRIPTS
;
382 void OpenTxSimulator::addTracebackDevice(QIODevice
* device
)
384 QMutexLocker
lckr(&m_mtxTbDevices
);
385 if (device
&& !tracebackDevices
.contains(device
))
386 tracebackDevices
.append(device
);
389 void OpenTxSimulator::removeTracebackDevice(QIODevice
* device
)
392 QMutexLocker
lckr(&m_mtxTbDevices
);
393 // no QVector::removeAll() in Qt < 5.4
395 foreach (QIODevice
* d
, tracebackDevices
) {
397 tracebackDevices
.remove(i
);
404 /*** Protected functions ***/
406 void OpenTxSimulator::run()
408 static uint32_t loops
= 0;
409 static QElapsedTimer ts
;
414 if (isStopRequested()) {
418 QString
err(getError());
419 emit
runtimeError(err
);
431 checkOutputsChanged();
434 if (!(loops
% (SIMULATOR_INTERFACE_HEARTBEAT_PERIOD
/ 10))) {
435 emit
heartbeat(loops
, simuTimerMicros() / 1000);
439 bool OpenTxSimulator::isStopRequested()
441 QMutexLocker
lckr(&m_mtxStopReq
);
442 return m_stopRequested
;
445 void OpenTxSimulator::setStopRequested(bool stop
)
447 QMutexLocker
lckr(&m_mtxStopReq
);
448 m_stopRequested
= stop
;
451 bool OpenTxSimulator::checkLcdChanged()
453 if (simuLcdRefresh
) {
454 simuLcdRefresh
= false;
455 emit
lcdChange(isBacklightEnabled());
461 void OpenTxSimulator::checkOutputsChanged()
463 static TxOutputs lastOutputs
;
464 static size_t chansDim
= DIM(channelOutputs
);
465 const static int16_t limit
= 512 * 2;
468 const uint8_t phase
= getFlightMode(); // opentx.cpp
469 const uint8_t mode
= getStickMode();
471 for (i
=0; i
< chansDim
; i
++) {
472 if (lastOutputs
.chans
[i
] != channelOutputs
[i
] || m_resetOutputsData
) {
473 emit
channelOutValueChange(i
, channelOutputs
[i
], (g_model
.extendedLimits
? limit
* LIMIT_EXT_PERCENT
/ 100 : limit
));
474 emit
outputValueChange(OUTPUT_SRC_CHAN_OUT
, i
, channelOutputs
[i
]);
475 lastOutputs
.chans
[i
] = channelOutputs
[i
];
477 if (lastOutputs
.ex_chans
[i
] != ex_chans
[i
] || m_resetOutputsData
) {
478 emit
channelMixValueChange(i
, ex_chans
[i
], limit
* 2);
479 emit
outputValueChange(OUTPUT_SRC_CHAN_MIX
, i
, ex_chans
[i
]);
480 lastOutputs
.ex_chans
[i
] = ex_chans
[i
];
484 for (i
=0; i
< MAX_LOGICAL_SWITCHES
; i
++) {
485 tmpVal
= (qint32
)GET_SWITCH_BOOL(SWSRC_SW1
+i
);
486 if (lastOutputs
.vsw
[i
] != (bool)tmpVal
|| m_resetOutputsData
) {
487 emit
virtualSwValueChange(i
, tmpVal
);
488 emit
outputValueChange(OUTPUT_SRC_VIRTUAL_SW
, i
, tmpVal
);
489 lastOutputs
.vsw
[i
] = tmpVal
;
493 for (i
=0; i
< Board::TRIM_AXIS_COUNT
; i
++) {
494 if (i
< 4) // swap axes
495 idx
= modn12x3
[4 * mode
+ i
];
499 tmpVal
= getTrimValue(getTrimFlightMode(phase
, idx
), idx
);
500 if (lastOutputs
.trims
[i
] != tmpVal
|| m_resetOutputsData
) {
501 emit
trimValueChange(i
, tmpVal
);
502 emit
outputValueChange(OUTPUT_SRC_TRIM_VALUE
, i
, tmpVal
);
503 lastOutputs
.trims
[i
] = tmpVal
;
507 tmpVal
= g_model
.extendedTrims
? TRIM_EXTENDED_MAX
: TRIM_MAX
;
508 if (lastOutputs
.trimRange
!= tmpVal
|| m_resetOutputsData
) {
509 emit
trimRangeChange(Board::TRIM_AXIS_COUNT
, -tmpVal
, tmpVal
);
510 emit
outputValueChange(OUTPUT_SRC_TRIM_RANGE
, Board::TRIM_AXIS_COUNT
, tmpVal
);
511 lastOutputs
.trimRange
= tmpVal
;
514 if (lastOutputs
.phase
!= phase
|| m_resetOutputsData
) {
515 emit
phaseChanged(phase
, getCurrentPhaseName());
516 emit
outputValueChange(OUTPUT_SRC_PHASE
, 0, qint16(phase
));
517 lastOutputs
.phase
= phase
;
520 #if defined(GVAR_VALUE) && defined(GVARS)
522 for (uint8_t gv
=0; gv
< MAX_GVARS
; gv
++) {
523 gvar
.prec
= g_model
.gvars
[gv
].prec
;
524 gvar
.unit
= g_model
.gvars
[gv
].unit
;
525 for (uint8_t fm
=0; fm
< MAX_FLIGHT_MODES
; fm
++) {
527 gvar
.value
= (int16_t)GVAR_VALUE(gv
, getGVarFlightMode(fm
, gv
));
529 if (lastOutputs
.gvars
[fm
][gv
] != tmpVal
|| m_resetOutputsData
) {
530 lastOutputs
.gvars
[fm
][gv
] = tmpVal
;
531 emit
gVarValueChange(gv
, tmpVal
);
532 emit
outputValueChange(OUTPUT_SRC_GVAR
, gv
, tmpVal
);
538 m_resetOutputsData
= false;
541 uint8_t OpenTxSimulator::getStickMode()
543 return limit
<uint8_t>(0, g_eeGeneral
.stickMode
, 3);
546 const char * OpenTxSimulator::getPhaseName(unsigned int phase
)
548 static char buff
[sizeof(g_model
.flightModeData
[0].name
)+1];
549 zchar2str(buff
, g_model
.flightModeData
[phase
].name
, sizeof(g_model
.flightModeData
[0].name
));
553 const QString
OpenTxSimulator::getCurrentPhaseName()
555 unsigned phase
= getFlightMode();
556 QString
name(getPhaseName(phase
));
558 name
= QString::number(phase
);
562 const char * OpenTxSimulator::getError()
564 return main_thread_error
;
567 const int OpenTxSimulator::voltageToAdc(const int volts
)
570 #if defined(PCBHORUS) || defined(PCBX7)
571 ret
= (float)volts
* 16.2f
;
572 #elif defined(PCBTARANIS) || defined(PCBSKY9X)
573 ret
= (float)volts
* 13.3f
;
575 ret
= (float)volts
* 14.15f
;
582 * OpenTxSimulatorFactory
585 class OpenTxSimulatorFactory
: public SimulatorFactory
588 OpenTxSimulatorFactory()
592 virtual SimulatorInterface
* create()
594 return new OpenTxSimulator();
597 virtual QString
name()
599 return QString(SIMULATOR_FLAVOUR
);
602 virtual Board::Type
type()
605 return Board::BOARD_HORUS_X12S
;
606 #elif defined(PCBX10)
607 return Board::BOARD_X10
;
609 return Board::BOARD_TARANIS_X7
;
610 #elif defined(PCBX9LITES)
611 return Board::BOARD_TARANIS_X9LITES
;
612 #elif defined(PCBX9LITE)
613 return Board::BOARD_TARANIS_X9LITE
;
614 #elif defined(PCBTARANIS)
615 return Board::BOARD_TARANIS_X9D
;
617 return Board::BOARD_9X_M64
;
622 extern "C" DLLEXPORT SimulatorFactory
* registerSimu()
624 return new OpenTxSimulatorFactory();