1
0
Fork 0
mirror of https://github.com/EdgeTX/edgetx.git synced 2025-07-17 05:15:17 +03:00
edgetx/companion/src/simulation/simulatorwidget.cpp
2021-01-14 13:30:54 +01:00

869 lines
27 KiB
C++

/*
* Copyright (C) OpenTX
*
* Based on code named
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "simulatorwidget.h"
#include "ui_simulatorwidget.h"
#include "appdata.h"
#include "appdebugmessagehandler.h"
#include "radiofaderwidget.h"
#include "radiokeywidget.h"
#include "radioknobwidget.h"
#include "radioswitchwidget.h"
#include "radiotrimwidget.h"
#include "radiouiaction.h"
#include "sdcard.h"
#include "simulateduiwidget.h"
#include "storage.h"
#include "virtualjoystickwidget.h"
#ifdef JOYSTICKS
#include "joystick.h"
#include "joystickdialog.h"
#endif
#include <QFile>
#include <QMessageBox>
#include <iostream>
using namespace Simulator;
SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface * simulator, quint8 flags):
QWidget(parent),
ui(new Ui::SimulatorWidget),
simulator(simulator),
firmware(getCurrentFirmware()),
radioSettings(GeneralSettings()),
m_board(getCurrentBoard()),
flags(flags)
{
ui->setupUi(this);
windowName = tr("Radio Simulator (%1)").arg(firmware->getName());
setWindowTitle(windowName);
switch(m_board) {
case Board::BOARD_TARANIS_X9LITE:
case Board::BOARD_TARANIS_X9LITES:
radioUiWidget = new SimulatedUIWidgetX9LITE(simulator, this);
break;
case Board::BOARD_TARANIS_X7:
case Board::BOARD_TARANIS_X7_ACCESS:
radioUiWidget = new SimulatedUIWidgetX7(simulator, this);
break;
case Board::BOARD_TARANIS_X9D:
case Board::BOARD_TARANIS_X9DP:
radioUiWidget = new SimulatedUIWidgetX9(simulator, this);
break;
case Board::BOARD_TARANIS_X9DP_2019:
radioUiWidget = new SimulatedUIWidgetX9D2019(simulator, this);
break;
case Board::BOARD_TARANIS_XLITE:
case Board::BOARD_TARANIS_XLITES:
radioUiWidget = new SimulatedUIWidgetXLITE(simulator, this);
break;
case Board::BOARD_TARANIS_X9E:
radioUiWidget = new SimulatedUIWidgetX9E(simulator, this);
break;
case Board::BOARD_HORUS_X12S:
radioUiWidget = new SimulatedUIWidgetX12(simulator, this);
break;
case Board::BOARD_X10:
case Board::BOARD_X10_EXPRESS:
radioUiWidget = new SimulatedUIWidgetX10(simulator, this);
break;
case Board::BOARD_JUMPER_T12:
radioUiWidget = new SimulatedUIWidgetJumperT12(simulator, this);
break;
case Board::BOARD_JUMPER_TLITE:
radioUiWidget = new SimulatedUIWidgetJumperTLITE(simulator, this);
break;
case Board::BOARD_JUMPER_T16:
radioUiWidget = new SimulatedUIWidgetJumperT16(simulator, this);
break;
case Board::BOARD_JUMPER_T18:
radioUiWidget = new SimulatedUIWidgetJumperT18(simulator, this);
break;
case Board::BOARD_RADIOMASTER_TX12:
radioUiWidget = new SimulatedUIWidgetTX12(simulator, this);
break;
case Board::BOARD_RADIOMASTER_TX16S:
radioUiWidget = new SimulatedUIWidgetTX16S(simulator, this);
break;
default:
radioUiWidget = new SimulatedUIWidget9X(simulator, this);
break;
}
foreach (keymapHelp_t item, radioUiWidget->getKeymapHelp())
keymapHelp.append(item);
ui->radioUiWidget->layout()->removeItem(ui->radioUiTempSpacer);
delete ui->radioUiTempSpacer;
ui->radioUiWidget->layout()->addWidget(radioUiWidget);
radioUiWidget->setFocusPolicy(Qt::WheelFocus);
radioUiWidget->setFocus();
connect(radioUiWidget, &SimulatedUIWidget::controlValueChange, this, &SimulatorWidget::onRadioWidgetValueChange);
connect(radioUiWidget, &SimulatedUIWidget::customStyleRequest, this, &SimulatorWidget::setUiAreaStyle);
vJoyLeft = new VirtualJoystickWidget(this, 'L');
ui->leftStickLayout->addWidget(vJoyLeft);
vJoyRight = new VirtualJoystickWidget(this, 'R', (m_board == Board::BOARD_TARANIS_XLITE || m_board == Board::BOARD_TARANIS_XLITES ? false : true)); // TODO: maybe remove trims for both joysticks and add a cross in the middle?
ui->rightStickLayout->addWidget(vJoyRight);
connect(vJoyLeft, &VirtualJoystickWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange);
connect(vJoyRight, &VirtualJoystickWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange);
connect(this, &SimulatorWidget::stickModeChange, vJoyLeft, &VirtualJoystickWidget::loadDefaultsForMode);
connect(this, &SimulatorWidget::stickModeChange, vJoyRight, &VirtualJoystickWidget::loadDefaultsForMode);
connect(simulator, &SimulatorInterface::trimValueChange, vJoyLeft, &VirtualJoystickWidget::setTrimValue);
connect(simulator, &SimulatorInterface::trimValueChange, vJoyRight, &VirtualJoystickWidget::setTrimValue);
connect(simulator, &SimulatorInterface::trimRangeChange, vJoyLeft, &VirtualJoystickWidget::setTrimRange);
connect(simulator, &SimulatorInterface::trimRangeChange, vJoyRight, &VirtualJoystickWidget::setTrimRange);
connect(this, &SimulatorWidget::simulatorInit, simulator, &SimulatorInterface::init);
connect(this, &SimulatorWidget::simulatorStart, simulator, &SimulatorInterface::start);
connect(this, &SimulatorWidget::simulatorStop, simulator, &SimulatorInterface::stop);
connect(this, &SimulatorWidget::inputValueChange, simulator, &SimulatorInterface::setInputValue);
connect(this, &SimulatorWidget::simulatorSdPathChange, simulator, &SimulatorInterface::setSdPath);
connect(this, &SimulatorWidget::simulatorVolumeGainChange, simulator, &SimulatorInterface::setVolumeGain);
connect(simulator, &SimulatorInterface::started, this, &SimulatorWidget::onSimulatorStarted);
connect(simulator, &SimulatorInterface::heartbeat, this, &SimulatorWidget::onSimulatorHeartbeat);
connect(simulator, &SimulatorInterface::runtimeError, this, &SimulatorWidget::onSimulatorError);
connect(simulator, &SimulatorInterface::phaseChanged, this, &SimulatorWidget::onPhaseChanged);
m_timer.setInterval(SIMULATOR_INTERFACE_HEARTBEAT_PERIOD * 6);
connect(&m_timer, &QTimer::timeout, this, &SimulatorWidget::onTimerEvent);
setupJoysticks();
// defaults
setRadioProfileId(g.sessionId());
setSdPath(g.profile[radioProfileId].sdPath());
}
SimulatorWidget::~SimulatorWidget()
{
shutdown();
delete radioUiWidget;
delete vJoyLeft;
delete vJoyRight;
#ifdef JOYSTICKS
delete joystick;
#endif
firmware = nullptr;
delete ui;
}
/*
* Public slots/setters
*/
void SimulatorWidget::setSdPath(const QString & sdPath)
{
setPaths(sdPath, radioDataPath);
}
void SimulatorWidget::setDataPath(const QString & dataPath)
{
setPaths(sdCardPath, dataPath);
}
void SimulatorWidget::setPaths(const QString & sdPath, const QString & dataPath)
{
sdCardPath = sdPath;
radioDataPath = dataPath;
emit simulatorSdPathChange(sdPath, dataPath);
}
void SimulatorWidget::setRadioSettings(const GeneralSettings settings)
{
radioSettings = settings;
}
/*
* This function can accept no parameters, a file name (QString is a QBA), or a data array. It will attempt to load radio settings data from one of
* several sources into a RadioData object, parse the data, and then pass it on as appropriate to the SimulatorInterface in start().
* If given no/blank <dataSource>, and setDataPath() was already called, then it will check that directory for "Horus-style" data files.
* If given a file name, set the <fromFile> parameter to 'true'. This will attempt to load radio settings from said file
* and later start the simulator interface in start() using the same data.
* If <dataSource> is a byte array of data, attempts to load radio settings from there and will also start the simulator interface
* with the same data when start() is called.
* If you already have a valid RadioData structure, call setRadioData() instead.
*/
bool SimulatorWidget::setStartupData(const QByteArray & dataSource, bool fromFile)
{
RadioData simuData;
quint16 ret = 1;
QString error;
QString fileName(dataSource);
// If <dataSource> is blank but we have a data path, use that for individual radio/model files.
if (dataSource.isEmpty() && !radioDataPath.isEmpty()) {
// If directory structure already exists, try to load data from there.
// FIXME : need Storage class to return formal error code, not just a boolean, because it would be better
// to avoid hard-coding paths like "RADIO" here. E.g. did it fail due to no data at all, or corrupt data, or...?
if (QDir(QString(radioDataPath).append("/RADIO")).exists()) {
SdcardFormat sdcard(radioDataPath);
if (!(ret = sdcard.load(simuData))) {
error = sdcard.error();
}
}
}
// Supposedly we're being given a file name to use, try that out.
else if (fromFile && !fileName.isEmpty()) {
Storage store = Storage(fileName);
ret = store.load(simuData);
if (!ret && QFile(fileName).exists()) {
error = store.error();
}
else {
if (fileName.endsWith(".otx", Qt::CaseInsensitive)) {
// no radios can work with .otx files directly, so we load contents into either
// a temporary folder (Horus) or local data array (other radios) which we'll save back to .otx upon exit
if ((ret = setRadioData(&simuData))) {
startupFromFile = false;
return true;
}
}
else {
// the binary file will be read/written directly by the fw interface, save the file name for simulator->start()
startupData = dataSource;
ret = 1;
}
}
}
// Assume a byte array of radio data was passed, load it.
else if (!dataSource.isEmpty()) {
ret = firmware->getEEpromInterface()->load(simuData, (uint8_t *)dataSource.constData(), Boards::getEEpromSize(m_board));
startupData = dataSource; // save the data for start()
}
// we're :-(
else {
ret = 0;
error = tr("Could not determine startup data source.");
}
if (!ret) {
if (error.isEmpty())
error = tr("Could not load data, possibly wrong format.");
QMessageBox::critical(this, tr("Data Load Error"), error);
return false;
}
radioSettings = simuData.generalSettings;
startupFromFile = fromFile;
return true;
}
bool SimulatorWidget::setRadioData(RadioData * radioData)
{
bool ret = true;
saveTempRadioData = (flags & SIMULATOR_FLAGS_STANDALONE);
if (IS_FAMILY_HORUS_OR_T16(m_board))
ret = useTempDataPath(true);
if (ret) {
if (radioDataPath.isEmpty()) {
startupData.fill(0, Boards::getEEpromSize(m_board));
if (firmware->getEEpromInterface()->save((uint8_t *)startupData.data(), *radioData, 0, firmware->getCapability(SimulatorVariant)) <= 0) {
ret = false;
}
}
else {
ret = saveRadioData(radioData, radioDataPath);
}
}
if (ret)
radioSettings = radioData->generalSettings;
return ret;
}
bool SimulatorWidget::setOptions(SimulatorOptions & options, bool withSave)
{
bool ret = false;
setSdPath(options.sdPath);
if (options.startupDataType == SimulatorOptions::START_WITH_FOLDER && !options.dataFolder.isEmpty()) {
setDataPath(options.dataFolder);
ret = setStartupData();
}
else if (options.startupDataType == SimulatorOptions::START_WITH_SDPATH && !options.sdPath.isEmpty()) {
setDataPath(options.sdPath);
ret = setStartupData();
}
else if (options.startupDataType == SimulatorOptions::START_WITH_FILE && !options.dataFile.isEmpty()) {
ret = setStartupData(options.dataFile.toLocal8Bit(), true);
}
else {
QString error = tr("Invalid startup data provided. Plese specify a proper file/path.");
QMessageBox::critical(this, tr("Simulator Startup Error"), error);
}
if (ret && withSave)
g.profile[radioProfileId].simulatorOptions(options);
return ret;
}
bool SimulatorWidget::saveRadioData(RadioData * radioData, const QString & path, QString * error)
{
QString dir = path;
if (dir.isEmpty())
dir = radioDataPath;
if (radioData && !dir.isEmpty()) {
SdcardFormat sdcard(dir);
bool ret = sdcard.write(*radioData);
if (!ret && error)
*error = sdcard.error();
return ret;
}
return false;
}
bool SimulatorWidget::useTempDataPath(bool deleteOnClose)
{
if (deleteTempRadioData)
deleteTempData();
QTemporaryDir tmpDir(QDir::tempPath() + "/otx-XXXXXX");
if (tmpDir.isValid()) {
setDataPath(tmpDir.path());
tmpDir.setAutoRemove(false);
deleteTempRadioData = deleteOnClose;
qDebug() << "Created temporary settings directory" << radioDataPath << "with delteOnClose:" << deleteOnClose;
return true;
}
else {
qCritical() << "ERROR : Failed to create temporary settings directory" << radioDataPath;
return false;
}
}
// This will save radio data from temporary folder structure back into an .otx file, eg. for Horus.
bool SimulatorWidget::saveTempData()
{
bool ret = false;
QString error;
QString file = g.profile[radioProfileId].simulatorOptions().dataFile;
if (!file.isEmpty()) {
RadioData radioData;
if (radioDataPath.isEmpty()) {
if (!startupData.isEmpty()) {
if (!QFile(file).exists()) {
QFile fh(file);
if (!fh.open(QIODevice::WriteOnly))
error = tr("Error saving data: could open file for writing: '%1'").arg(file);
else
fh.close();
}
if (!firmware->getEEpromInterface()->load(radioData, (uint8_t *)startupData.constData(), Boards::getEEpromSize(m_board))) {
error = tr("Error saving data: could not get data from simulator interface.");
}
else {
radioData.fixModelFilenames();
ret = true;
}
}
}
else {
SdcardFormat sdcard(radioDataPath);
if (!(ret = sdcard.load(radioData))) {
error = sdcard.error();
}
}
if (ret) {
Storage store(file);
if (!(ret = store.write(radioData)))
error = store.error();
else
qInfo() << "Saved radio data to file" << file;
}
}
if (!ret) {
if (error.isEmpty())
error = tr("An unexpected error occurred while attempting to save radio data to file '%1'.").arg(file);
QMessageBox::critical(this, tr("Data Save Error"), error);
}
return ret;
}
void SimulatorWidget::deleteTempData()
{
if (!radioDataPath.isEmpty()) {
QDir tpath(radioDataPath);
qDebug() << "Deleting temporary settings directory" << tpath.absolutePath();
tpath.removeRecursively();
tpath.rmdir(radioDataPath); // for some reason this is necessary to remove the base folder
}
}
void SimulatorWidget::saveState()
{
SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions();
saveRadioWidgetsState(opts.controlsState);
g.profile[radioProfileId].simulatorOptions(opts);
}
void SimulatorWidget::setUiAreaStyle(const QString & style)
{
setStyleSheet(style);
}
void SimulatorWidget::captureScreenshot(bool)
{
if (radioUiWidget)
radioUiWidget->captureScreenshot();
}
/*
* Startup
*/
void SimulatorWidget::start()
{
emit simulatorInit(); // init simulator default I/O values
setupRadioWidgets();
restoreRadioWidgetsState();
bool tests = !(flags & SIMULATOR_FLAGS_NOTX);
if (!startupData.isEmpty()) {
if (startupFromFile) {
emit simulatorStart(startupData.constData(), tests);
}
else {
simulator->setRadioData(startupData);
emit simulatorStart((const char *)0, tests);
}
}
else {
emit simulatorStart((const char *)0, tests);
}
}
void SimulatorWidget::stop()
{
emit simulatorStop();
QElapsedTimer tmout;
tmout.start();
// block until simulator stops or times out
while (simulator->isRunning()) {
if (tmout.hasExpired(2000)) {
onSimulatorError("Timeout while trying to stop simulation!");
break;
}
QApplication::processEvents();
}
onSimulatorStopped();
}
void SimulatorWidget::onSimulatorStarted()
{
m_heartbeatTimer.start();
m_timer.start();
}
void SimulatorWidget::onSimulatorStopped()
{
m_timer.stop();
m_heartbeatTimer.invalidate();
if (simulator && !simulator->isRunning() && saveTempRadioData) {
startupData.fill(0, Boards::getEEpromSize(m_board));
simulator->readRadioData(startupData);
}
}
void SimulatorWidget::restart()
{
stop();
saveState();
setStartupData(startupData, startupFromFile);
start();
}
void SimulatorWidget::shutdown()
{
stop();
saveState();
if (saveTempRadioData)
saveTempData();
if (deleteTempRadioData)
deleteTempData();
}
/*
* Setup
*/
void SimulatorWidget::setRadioProfileId(int value)
{
Q_ASSERT(value >= 0);
radioProfileId = value;
emit simulatorVolumeGainChange(g.profile[radioProfileId].volumeGain());
}
void SimulatorWidget::setupRadioWidgets()
{
QString wname;
int i, midpos;
const int ttlSticks = Boards::getCapability(m_board, Board::Sticks);
const int ttlSwitches = Boards::getCapability(m_board, Board::Switches);
const int ttlKnobs = Boards::getCapability(m_board, Board::Pots);
const int ttlFaders = Boards::getCapability(m_board, Board::Sliders);
const int extraTrims = Boards::getCapability(m_board, Board::NumTrims) - ttlSticks;
// First clear out any existing widgets.
foreach (RadioWidget * rw, m_radioWidgets) {
switch(rw->getType()) {
case RadioWidget::RADIO_WIDGET_SWITCH :
case RadioWidget::RADIO_WIDGET_KNOB :
ui->radioWidgetsHTLayout->removeWidget(rw);
break;
case RadioWidget::RADIO_WIDGET_FADER :
case RadioWidget::RADIO_WIDGET_TRIM :
ui->VCGridLayout->removeWidget(rw);
break;
default :
break;
}
disconnect(rw, 0, this, 0);
disconnect(this, 0, rw, 0);
rw->deleteLater();
}
m_radioWidgets.clear();
// Now set up new widgets.
// switches
Board::SwitchType swcfg;
for (i = 0; i < ttlSwitches; ++i) {
if (radioSettings.switchConfig[i] == Board::SWITCH_NOT_AVAILABLE)
continue;
swcfg = Board::SwitchType(radioSettings.switchConfig[i]);
wname = RawSource(RawSourceType::SOURCE_TYPE_SWITCH, i).toString(nullptr, &radioSettings);
RadioSwitchWidget * sw = new RadioSwitchWidget(swcfg, wname, -1, ui->radioWidgetsHT);
sw->setIndex(i);
ui->radioWidgetsHTLayout->addWidget(sw);
m_radioWidgets.append(sw);
}
midpos = (int)floorf(m_radioWidgets.size() / 2.0f);
// pots in middle of switches
for (i = 0; i < ttlKnobs; ++i) {
if (!radioSettings.isPotAvailable(i))
continue;
wname = RawSource(RawSourceType::SOURCE_TYPE_STICK, ttlSticks + i).toString(nullptr, &radioSettings);
RadioKnobWidget * pot = new RadioKnobWidget(Board::PotType(radioSettings.potConfig[i]), wname, 0, ui->radioWidgetsHT);
pot->setIndex(i);
ui->radioWidgetsHTLayout->insertWidget(midpos++, pot);
m_radioWidgets.append(pot);
}
// faders between sticks
int c = extraTrims / 2; // leave space for any extra trims
for (i = 0; i < ttlFaders; ++i) {
if (!radioSettings.isSliderAvailable(i))
continue;
wname = RawSource(RawSourceType::SOURCE_TYPE_STICK, ttlSticks + ttlKnobs + i).toString(nullptr, &radioSettings);
RadioFaderWidget * sl = new RadioFaderWidget(wname, 0, ui->radioWidgetsVC);
sl->setIndex(i);
ui->VCGridLayout->addWidget(sl, 0, c++, 1, 1);
m_radioWidgets.append(sl);
}
// extra trims around faders
int tridx = Board::TRIM_AXIS_COUNT;
int trswidx = Board::TRIM_SW_COUNT;
for (i = extraTrims; i > 0; --i) {
trswidx -= 2;
--tridx;
wname = RawSource(RawSourceType::SOURCE_TYPE_TRIM, tridx).toString(nullptr, &radioSettings);
wname = wname.left(1) % wname.right(1);
RadioTrimWidget * tw = new RadioTrimWidget(Qt::Vertical, ui->radioWidgetsVC);
tw->setIndices(tridx, trswidx, trswidx + 1);
tw->setLabelText(wname);
if (i <= extraTrims / 2)
c = 0;
ui->VCGridLayout->addWidget(tw, 0, c++, 1, 1);
connect(simulator, &SimulatorInterface::trimValueChange, tw, &RadioTrimWidget::setTrimValue);
connect(simulator, &SimulatorInterface::trimRangeChange, tw, &RadioTrimWidget::setTrimRangeQual);
m_radioWidgets.append(tw);
}
// connect all the widgets
foreach (RadioWidget * rw, m_radioWidgets) {
connect(rw, &RadioWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange);
connect(this, &SimulatorWidget::widgetValueChange, rw, &RadioWidget::setValueQual);
connect(this, &SimulatorWidget::widgetStateChange, rw, &RadioWidget::setStateData);
}
}
void SimulatorWidget::setupJoysticks()
{
#ifdef JOYSTICKS
bool joysticksEnabled = false;
if (g.jsSupport() && g.jsCtrl() > -1) {
if (!joystick)
joystick = new Joystick(this);
else
joystick->close();
if (joystick && joystick->open(g.jsCtrl())) {
int numAxes = std::min(joystick->numAxes, MAX_JOYSTICKS);
for (int j=0; j<numAxes; j++) {
joystick->sensitivities[j] = 0;
joystick->deadzones[j] = 0;
}
connect(joystick, &Joystick::axisValueChanged, this, &SimulatorWidget::onjoystickAxisValueChanged);
if (vJoyLeft)
connect(this, &SimulatorWidget::stickValueChange, vJoyLeft, &VirtualJoystickWidget::setStickAxisValue);
if (vJoyRight)
connect(this, &SimulatorWidget::stickValueChange, vJoyRight, &VirtualJoystickWidget::setStickAxisValue);
joysticksEnabled = true;
}
else {
QMessageBox::critical(this, CPN_STR_TTL_WARNING, tr("Cannot open joystick, joystick disabled"));
}
}
else if (joystick) {
joystick->close();
disconnect(joystick, 0, this, 0);
if (vJoyLeft)
disconnect(this, 0, vJoyLeft, 0);
if (vJoyRight)
disconnect(this, 0, vJoyRight, 0);
joystick->deleteLater();
joystick = nullptr;
}
if (vJoyRight)
vJoyRight->setStickConstraint((VirtualJoystickWidget::HOLD_X | VirtualJoystickWidget::HOLD_Y), joysticksEnabled);
if (vJoyLeft)
vJoyLeft->setStickConstraint((VirtualJoystickWidget::HOLD_X | VirtualJoystickWidget::HOLD_Y), joysticksEnabled);
#endif
}
void SimulatorWidget::restoreRadioWidgetsState()
{
// All RadioWidgets
RadioWidget::RadioWidgetState state;
QList<QByteArray> states = g.profile[radioProfileId].simulatorOptions().controlsState;
foreach (QByteArray ba, states) {
QDataStream stream(ba);
stream >> state;
emit widgetStateChange(state);
}
// Set throttle stick down and locked, side depends on mode
emit stickModeChange(radioSettings.stickMode);
// TODO : custom voltages
qint16 volts = radioSettings.vBatWarn + 20; // 1V above min
emit inputValueChange(SimulatorInterface::INPUT_SRC_TXVIN, 0, volts);
}
void SimulatorWidget::saveRadioWidgetsState(QList<QByteArray> & state)
{
if (m_radioWidgets.size()) {
if (g.simuSW()) {
state.clear();
foreach (RadioWidget * rw, m_radioWidgets)
state.append(rw->getStateData());
}
}
}
/*
* Event handlers/private slots
*/
void SimulatorWidget::mousePressEvent(QMouseEvent *event)
{
if (radioUiWidget)
radioUiWidget->mousePressEvent(event);
}
void SimulatorWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (radioUiWidget)
radioUiWidget->mouseReleaseEvent(event);
}
void SimulatorWidget::wheelEvent(QWheelEvent *event)
{
if (radioUiWidget)
radioUiWidget->wheelEvent(event);
}
void SimulatorWidget::onTimerEvent()
{
if (m_heartbeatTimer.isValid() && m_heartbeatTimer.hasExpired(m_timer.interval())) {
onSimulatorError("Heartbeat timeout!");
onSimulatorStopped();
}
}
void SimulatorWidget::onSimulatorHeartbeat(qint32 loops, qint64 timestamp)
{
Q_UNUSED(loops)
Q_UNUSED(timestamp)
m_heartbeatTimer.start();
#if 0
static qint64 lastTs = 0;
if (!(loops % 1000)) {
qDebug() << "loops:" << loops << "ts:" << timestamp << "ts-delta:" << timestamp - lastTs << "This:" << QThread::currentThread() << "Simu:" << simulator->thread();
lastTs = timestamp;
}
#endif
}
void SimulatorWidget::onSimulatorError(const QString & error)
{
QMessageBox::critical(this, windowName, tr("Radio firmware error: %1").arg(error.isEmpty() ? "Unknown reason" : error));
}
void SimulatorWidget::onPhaseChanged(qint32 phase, const QString & name)
{
setWindowTitle(windowName + tr(" - Flight Mode %1 (#%2)").arg(name).arg(phase));
}
void SimulatorWidget::onRadioWidgetValueChange(const RadioWidget::RadioWidgetType type, const int index, int value)
{
//qDebug() << type << index << value;
if (!simulator || index < 0)
return;
SimulatorInterface::InputSourceType inpType = SimulatorInterface::INPUT_SRC_NONE;
switch (type) {
case RadioWidget::RADIO_WIDGET_SWITCH :
inpType = SimulatorInterface::INPUT_SRC_SWITCH;
break;
case RadioWidget::RADIO_WIDGET_KNOB :
inpType = SimulatorInterface::INPUT_SRC_KNOB;
break;
case RadioWidget::RADIO_WIDGET_FADER :
inpType = SimulatorInterface::INPUT_SRC_SLIDER;
break;
case RadioWidget::RADIO_WIDGET_TRIM :
switch (value) {
case RadioWidget::RADIO_TRIM_BTN_ON :
inpType = SimulatorInterface::INPUT_SRC_TRIM_SW;
value = 1;
break;
case RadioWidget::RADIO_TRIM_BTN_OFF :
inpType = SimulatorInterface::INPUT_SRC_TRIM_SW;
value = 0;
break;
default :
inpType = SimulatorInterface::INPUT_SRC_TRIM;
break;
}
break;
case RadioWidget::RADIO_WIDGET_STICK :
inpType = SimulatorInterface::INPUT_SRC_STICK;
break;
case RadioWidget::RADIO_WIDGET_KEY :
inpType = SimulatorInterface::INPUT_SRC_KEY;
break;
default :
return;
}
emit inputValueChange(inpType, index, value);
}
void SimulatorWidget::onjoystickAxisValueChanged(int axis, int value)
{
#ifdef JOYSTICKS
static const int ttlSticks = 4;
static const int ttlKnobs = Boards::getCapability(m_board, Board::Pots);
static const int ttlFaders = Boards::getCapability(m_board, Board::Sliders);
static const int valueRange = 1024;
if (!joystick || axis >= MAX_JOYSTICKS)
return;
int dlta;
int stick = g.joystick[axis].stick_axe();
if (stick < 0 || stick >= ttlSticks + ttlKnobs + ttlFaders)
return;
int stickval = valueRange * (value - g.joystick[axis].stick_med());
if (value > g.joystick[axis].stick_med()) {
if ((dlta = g.joystick[axis].stick_max() - g.joystick[axis].stick_med()))
stickval /= dlta;
}
else if ((dlta = g.joystick[axis].stick_med() - g.joystick[axis].stick_min())) {
stickval /= dlta;
}
if (g.joystick[axis].stick_inv())
stickval *= -1;
if (stick < ttlSticks) {
emit stickValueChange(stick, stickval);
}
else {
stick -= ttlSticks;
if (stick < ttlKnobs)
emit widgetValueChange(RadioWidget::RADIO_WIDGET_KNOB, stick, stickval);
else
emit widgetValueChange(RadioWidget::RADIO_WIDGET_FADER, stick, stickval);
}
#endif
}