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
29 #define GET_SWITCH_BOOL(sw__) getSwitch((sw__), 0);
31 #define GET_SWITCH_BOOL(sw__) getSwitch(sw__);
34 #define OTXS_DBG qDebug() << "(" << simuTimerMicros() << "us)"
36 int16_t g_anas
[Analogs::NUM_ANALOGS
];
37 QVector
<QIODevice
*> OpenTxSimulator::tracebackDevices
;
39 uint16_t anaIn(uint8_t chan
)
44 uint16_t getAnalogValue(uint8_t index
)
49 void firmwareTraceCb(const char * text
)
51 foreach (QIODevice
* dev
, OpenTxSimulator::tracebackDevices
) {
57 OpenTxSimulator::OpenTxSimulator() :
60 m_resetOutputsData(true),
61 m_stopRequested(false)
63 tracebackDevices
.clear();
64 traceCallback
= firmwareTraceCb
;
67 OpenTxSimulator::~OpenTxSimulator()
70 tracebackDevices
.clear();
79 while (isRunning() && !tmout
.hasExpired(1000))
82 //qDebug() << "Deleting OpenTxSimulator";
85 QString
OpenTxSimulator::name()
87 return QString(SIMULATOR_FLAVOUR
);
90 bool OpenTxSimulator::isRunning()
92 QMutexLocker
lckr(&m_mtxSimuMain
);
93 return (bool)main_thread_running
;
96 void OpenTxSimulator::init()
103 // make sure we create & control the timer from current thread
104 m_timer10ms
= new QTimer();
105 m_timer10ms
->setInterval(10);
106 connect(m_timer10ms
, &QTimer::timeout
, this, &OpenTxSimulator::run
);
107 connect(this, SIGNAL(started()), m_timer10ms
, SLOT(start()));
108 connect(this, SIGNAL(stopped()), m_timer10ms
, SLOT(stop()));
111 m_resetOutputsData
= true;
112 setStopRequested(false);
114 QMutexLocker
lckr(&m_mtxSimuMain
);
115 memset(g_anas
, 0, sizeof(g_anas
));
119 void OpenTxSimulator::start(const char * filename
, bool tests
)
123 OTXS_DBG
<< "file:" << filename
<< "tests:" << tests
;
125 QMutexLocker
lckr(&m_mtxSimuMain
);
126 QMutexLocker
slckr(&m_mtxSettings
);
127 StartEepromThread(filename
);
128 StartAudioThread(volumeGain
);
129 StartSimu(tests
, simuSdDirectory
.toLatin1().constData(), simuSettingsDirectory
.toLatin1().constData());
132 QTimer::singleShot(0, this, SLOT(run())); // old style for Qt < 5.4
135 void OpenTxSimulator::stop()
141 setStopRequested(true);
143 QMutexLocker
lckr(&m_mtxSimuMain
);
151 void OpenTxSimulator::setSdPath(const QString
& sdPath
, const QString
& settingsPath
)
153 QMutexLocker
lckr(&m_mtxSettings
);
154 simuSdDirectory
= sdPath
;
155 simuSettingsDirectory
= settingsPath
;
158 void OpenTxSimulator::setVolumeGain(const int value
)
160 QMutexLocker
lckr(&m_mtxSettings
);
164 void OpenTxSimulator::setRadioData(const QByteArray
& data
)
166 #if defined(EEPROM_SIZE)
167 QMutexLocker
lckr(&m_mtxRadioData
);
168 memcpy(eeprom
, data
.data(), qMin
<int>(EEPROM_SIZE
, data
.size()));
172 void OpenTxSimulator::readRadioData(QByteArray
& dest
)
174 #if defined(EEPROM_SIZE)
175 QMutexLocker
lckr(&m_mtxRadioData
);
176 memcpy(dest
.data(), eeprom
, std::min
<int>(EEPROM_SIZE
, dest
.size()));
180 uint8_t * OpenTxSimulator::getLcd()
182 return (uint8_t *)simuLcdBuf
;
185 void OpenTxSimulator::setAnalogValue(uint8_t index
, int16_t value
)
187 static int dim
= DIM(g_anas
);
189 g_anas
[index
] = value
;
192 void OpenTxSimulator::setSwitch(uint8_t swtch
, int8_t state
)
194 simuSetSwitch(swtch
, state
);
197 void OpenTxSimulator::setKey(uint8_t key
, bool state
)
199 simuSetKey(key
, state
);
202 void OpenTxSimulator::setTrimSwitch(uint8_t trim
, bool state
)
204 simuSetTrim(trim
, state
);
207 void OpenTxSimulator::setTrim(unsigned int idx
, int value
)
210 if (i
< 4) // swap axes
211 i
= modn12x3
[4 * getStickMode() + idx
];
212 uint8_t phase
= getTrimFlightMode(getFlightMode(), i
);
215 if (!setTrimValue(phase
, i
, value
)) {
216 QTimer
*timer
= new QTimer(this);
217 timer
->setSingleShot(true);
218 connect(timer
, &QTimer::timeout
, [=]() {
219 emit
trimValueChange(idx
, 0);
220 emit
outputValueChange(OUTPUT_SRC_TRIM_VALUE
, idx
, 0);
221 timer
->deleteLater();
226 setTrimValue(phase
, i
, value
);
230 void OpenTxSimulator::setTrainerInput(unsigned int inputNumber
, int16_t value
)
232 static unsigned dim
= DIM(ppmInput
);
233 //setTrainerTimeout(100);
234 if (inputNumber
< dim
)
235 ppmInput
[inputNumber
] = qMin(qMax((int16_t)-512, value
), (int16_t)512);
238 void OpenTxSimulator::setInputValue(int type
, uint8_t index
, int16_t value
)
240 //qDebug() << type << index << value << this->thread();
242 case INPUT_SRC_ANALOG
:
243 case INPUT_SRC_STICK
:
244 setAnalogValue(index
, value
);
246 case INPUT_SRC_KNOB
:
247 setAnalogValue(index
+ NUM_STICKS
, value
);
249 case INPUT_SRC_SLIDER
:
250 setAnalogValue(index
+ NUM_STICKS
+ NUM_POTS
, value
);
252 case INPUT_SRC_TXVIN
:
253 setAnalogValue(Analogs::TX_VOLTAGE
, voltageToAdc(value
));
255 case INPUT_SRC_SWITCH
:
256 setSwitch(index
, (int8_t)value
);
258 case INPUT_SRC_TRIM_SW
:
259 setTrimSwitch(index
, (bool)value
);
261 case INPUT_SRC_TRIM
:
262 setTrim(index
, value
);
265 setKey(index
, (bool)value
);
267 case INPUT_SRC_TRAINER
:
268 setTrainerInput(index
, value
);
270 case INPUT_SRC_ROTENC
: // TODO
276 void OpenTxSimulator::rotaryEncoderEvent(int steps
)
278 #if defined(ROTARY_ENCODER_NAVIGATION)
279 ROTARY_ENCODER_NAVIGATION_VALUE
+= steps
* ROTARY_ENCODER_GRANULARITY
;
281 // TODO : this should probably be handled in the GUI
289 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
290 QTimer::singleShot(10, [this, key
]() { setKey(key
, 0); });
292 QTimer
*timer
= new QTimer(this);
293 timer
->setSingleShot(true);
294 connect(timer
, &QTimer::timeout
, [=]() {
296 timer
->deleteLater();
300 #endif // defined(ROTARY_ENCODER_NAVIGATION)
303 void OpenTxSimulator::setTrainerTimeout(uint16_t ms
)
305 ppmInputValidityTimer
= ms
;
308 void OpenTxSimulator::sendTelemetry(uint8_t * data
, unsigned int len
)
311 #if defined(TELEMETRY_FRSKY_SPORT)
312 sportProcessTelemetryPacket(data
);
318 uint8_t OpenTxSimulator::getSensorInstance(uint16_t id
, uint8_t defaultValue
)
320 #if defined(TELEMETRY_FRSKY_SPORT)
321 for (int i
= 0; i
< MAX_TELEMETRY_SENSORS
; i
++) {
322 if (isTelemetryFieldAvailable(i
)) {
323 TelemetrySensor
* sensor
= &g_model
.telemetrySensors
[i
];
324 if (sensor
->id
== id
) {
325 return sensor
->instance
;
335 uint16_t OpenTxSimulator::getSensorRatio(uint16_t id
)
337 #if defined(TELEMETRY_FRSKY_SPORT)
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
;
352 const int OpenTxSimulator::getCapability(Capability cap
)
362 case CAP_ROTARY_ENC
:
363 #ifdef ROTARY_ENCODERS
364 ret
= ROTARY_ENCODERS
;
368 case CAP_ROTARY_ENC_NAV
:
369 #ifdef ROTARY_ENCODER_NAVIGATION
374 case CAP_TELEM_FRSKY_SPORT
:
375 #ifdef TELEMETRY_FRSKY_SPORT
383 void OpenTxSimulator::setLuaStateReloadPermanentScripts()
386 luaState
= INTERPRETER_RELOAD_PERMANENT_SCRIPTS
;
390 void OpenTxSimulator::addTracebackDevice(QIODevice
* device
)
392 QMutexLocker
lckr(&m_mtxTbDevices
);
393 if (device
&& !tracebackDevices
.contains(device
))
394 tracebackDevices
.append(device
);
397 void OpenTxSimulator::removeTracebackDevice(QIODevice
* device
)
400 QMutexLocker
lckr(&m_mtxTbDevices
);
401 // no QVector::removeAll() in Qt < 5.4
403 foreach (QIODevice
* d
, tracebackDevices
) {
405 tracebackDevices
.remove(i
);
412 /*** Protected functions ***/
414 void OpenTxSimulator::run()
416 static uint32_t loops
= 0;
417 static QElapsedTimer ts
;
422 if (isStopRequested()) {
426 QString
err(getError());
427 emit
runtimeError(err
);
439 checkOutputsChanged();
442 if (!(loops
% (SIMULATOR_INTERFACE_HEARTBEAT_PERIOD
/ 10))) {
443 emit
heartbeat(loops
, simuTimerMicros() / 1000);
447 bool OpenTxSimulator::isStopRequested()
449 QMutexLocker
lckr(&m_mtxStopReq
);
450 return m_stopRequested
;
453 void OpenTxSimulator::setStopRequested(bool stop
)
455 QMutexLocker
lckr(&m_mtxStopReq
);
456 m_stopRequested
= stop
;
459 bool OpenTxSimulator::checkLcdChanged()
461 if (simuLcdRefresh
) {
462 simuLcdRefresh
= false;
463 emit
lcdChange(isBacklightEnabled());
469 void OpenTxSimulator::checkOutputsChanged()
471 static TxOutputs lastOutputs
;
472 static size_t chansDim
= DIM(channelOutputs
);
475 uint8_t phase
= getFlightMode(); // opentx.cpp
476 uint8_t mode
= getStickMode();
478 for (i
=0; i
< chansDim
; i
++) {
479 if (lastOutputs
.chans
[i
] != channelOutputs
[i
] || m_resetOutputsData
) {
480 emit
channelOutValueChange(i
, channelOutputs
[i
]);
481 emit
channelMixValueChange(i
, ex_chans
[i
]);
482 emit
outputValueChange(OUTPUT_SRC_CHAN_OUT
, i
, channelOutputs
[i
]);
483 emit
outputValueChange(OUTPUT_SRC_CHAN_MIX
, i
, ex_chans
[i
]);
484 lastOutputs
.chans
[i
] = channelOutputs
[i
];
488 for (i
=0; i
< MAX_LOGICAL_SWITCHES
; i
++) {
489 tmpVal
= (qint32
)GET_SWITCH_BOOL(SWSRC_SW1
+i
);
490 if (lastOutputs
.vsw
[i
] != (bool)tmpVal
|| m_resetOutputsData
) {
491 emit
virtualSwValueChange(i
, tmpVal
);
492 emit
outputValueChange(OUTPUT_SRC_VIRTUAL_SW
, i
, tmpVal
);
493 lastOutputs
.vsw
[i
] = tmpVal
;
497 for (i
=0; i
< Board::TRIM_AXIS_COUNT
; i
++) {
498 if (i
< 4) // swap axes
499 idx
= modn12x3
[4 * mode
+ i
];
503 tmpVal
= getTrimValue(getTrimFlightMode(phase
, idx
), idx
);
504 if (lastOutputs
.trims
[i
] != tmpVal
|| m_resetOutputsData
) {
505 emit
trimValueChange(i
, tmpVal
);
506 emit
outputValueChange(OUTPUT_SRC_TRIM_VALUE
, i
, tmpVal
);
507 lastOutputs
.trims
[i
] = tmpVal
;
511 tmpVal
= g_model
.extendedTrims
? TRIM_EXTENDED_MAX
: TRIM_MAX
;
512 if (lastOutputs
.trimRange
!= tmpVal
|| m_resetOutputsData
) {
513 emit
trimRangeChange(Board::TRIM_AXIS_COUNT
, -tmpVal
, tmpVal
);
514 emit
outputValueChange(OUTPUT_SRC_TRIM_RANGE
, Board::TRIM_AXIS_COUNT
, tmpVal
);
515 lastOutputs
.trimRange
= tmpVal
;
518 if (lastOutputs
.phase
!= phase
|| m_resetOutputsData
) {
519 emit
phaseChanged(phase
, getCurrentPhaseName());
520 emit
outputValueChange(OUTPUT_SRC_PHASE
, 0, qint16(phase
));
521 lastOutputs
.phase
= phase
;
524 #if defined(GVAR_VALUE) && defined(GVARS)
526 for (uint8_t fm
=0; fm
< MAX_FLIGHT_MODES
; fm
++) {
528 for (uint8_t gv
=0; gv
< MAX_GVARS
; gv
++) {
529 tmpVal
= GVAR_VALUE(gv
, getGVarFlightMode(fm
, gv
));
530 if (lastOutputs
.gvars
[fm
][gv
] != tmpVal
|| m_resetOutputsData
) {
531 lastOutputs
.gvars
[fm
][gv
] = tmpVal
;
532 gvar
.value
= (int16_t)tmpVal
;
534 emit
gVarValueChange(gv
, tmpVal
);
535 emit
outputValueChange(OUTPUT_SRC_GVAR
, gv
, tmpVal
);
541 m_resetOutputsData
= false;
544 uint8_t OpenTxSimulator::getStickMode()
546 return limit
<uint8_t>(0, g_eeGeneral
.stickMode
, 3);
549 const char * OpenTxSimulator::getPhaseName(unsigned int phase
)
551 static char buff
[sizeof(g_model
.flightModeData
[0].name
)+1];
552 zchar2str(buff
, g_model
.flightModeData
[phase
].name
, sizeof(g_model
.flightModeData
[0].name
));
556 const QString
OpenTxSimulator::getCurrentPhaseName()
558 unsigned phase
= getFlightMode();
559 QString
name(getPhaseName(phase
));
561 name
= QString::number(phase
);
565 const char * OpenTxSimulator::getError()
567 return main_thread_error
;
570 const int OpenTxSimulator::voltageToAdc(const int volts
)
573 #if defined(PCBHORUS) || defined(PCBX7)
574 ret
= (float)volts
* 16.2f
;
575 #elif defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBSKY9X)
576 ret
= (float)volts
* 13.3f
;
577 #elif defined(PCBGRUVIN9X)
578 ret
= (float)volts
* 1.63f
;
580 ret
= (float)volts
* 14.15f
;
587 * OpenTxSimulatorFactory
590 class OpenTxSimulatorFactory
: public SimulatorFactory
593 OpenTxSimulatorFactory()
597 virtual SimulatorInterface
* create()
599 return new OpenTxSimulator();
602 virtual QString
name()
604 return QString(SIMULATOR_FLAVOUR
);
607 virtual Board::Type
type()
610 return Board::BOARD_X12S
;
611 #elif defined(PCBX10)
612 return Board::BOARD_X10
;
613 #elif defined(PCBFLAMENCO)
614 return Board::BOARD_FLAMENCO
;
616 return Board::BOARD_TARANIS_X7
;
617 #elif defined(PCBTARANIS)
618 return Board::BOARD_TARANIS_X9D
;
620 return Board::BOARD_STOCK
;
625 extern "C" DLLEXPORT SimulatorFactory
* registerSimu()
627 return new OpenTxSimulatorFactory();