Make TX volatge for simu more flexible (#7124)
[opentx.git] / companion / src / simulator.cpp
blob5cf10c8dd802f9ab3d8906f2dc3482b767c80548
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
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.
21 #include <QApplication>
22 #include <QDateTime>
23 #include <QCommandLineParser>
24 #include <QMessageBox>
25 #include <QString>
26 #include <QTextStream>
27 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
28 #include <SDL.h>
29 #undef main
30 #endif
32 #include "appdata.h"
33 #include "appdebugmessagehandler.h"
34 #include "constants.h"
35 #include "customdebug.h"
36 #include "eeprominterface.h"
37 #include "simulator.h"
38 #include "simulatormainwindow.h"
39 #include "simulatorstartupdialog.h"
40 #include "storage.h"
41 #include "translations.h"
42 #include "version.h"
44 using namespace Simulator;
46 QFile simuDbgLog;
47 int finish(int exitCode);
49 void showMessage(const QString & message, enum QMessageBox::Icon icon = QMessageBox::NoIcon, bool useConsole = false)
51 if (useConsole) {
52 if (icon < QMessageBox::Warning)
53 QTextStream(stdout) << message << endl;
54 else
55 QTextStream(stderr) << message << endl;
57 return;
59 // use GUI
60 QMessageBox msgBox;
61 msgBox.setText("<html><body><pre>" + message.toHtmlEscaped() + "</pre></body></html>");
62 msgBox.setIcon(icon);
63 msgBox.setWindowTitle(QApplication::translate("SimulatorMain", "OpenTx Simulator"));
64 msgBox.exec();
67 const QString sharedHelpText()
69 QString ret;
70 QTextStream stream(&ret);
71 // list all available profiles
72 stream << endl << QApplication::translate("SimulatorMain", "Available profiles:") << endl;
73 QMapIterator<int, QString> pi(g.getActiveProfiles());
74 while (pi.hasNext()) {
75 pi.next();
76 stream << "\t" << QApplication::translate("SimulatorMain", "ID: ") << pi.key() << "; " << QApplication::translate("SimulatorMain", "Name: ") << pi.value() << endl;
78 // list all available radios
79 stream << endl << QApplication::translate("SimulatorMain", "Available radios:") << endl;
80 foreach(QString name, SimulatorLoader::getAvailableSimulators()) {
81 stream << "\t" << name << endl;
83 return ret;
86 void showHelp(QCommandLineParser & parser, const QString & addMsg = QString(), int exitCode = 0) {
87 QString msg = "\n";
88 if (!addMsg.isEmpty())
89 msg.append(addMsg).append("\n\n");
90 msg.append(parser.helpText());
91 msg.append(sharedHelpText());
92 // display
93 showMessage(msg, (exitCode ? QMessageBox::Warning : QMessageBox::Information));
96 enum CommandLineParseResult
98 CommandLineNone,
99 CommandLineFound,
100 CommandLineExitOk,
101 CommandLineExitErr
104 CommandLineParseResult cliOptions(SimulatorOptions * simOptions, int * profileId)
106 QCommandLineParser cliOptions;
107 bool cliOptsFound = false;
108 int pId = *profileId;
110 const QCommandLineOption optHelp = cliOptions.addHelpOption();
111 const QCommandLineOption optVer = cliOptions.addVersionOption();
113 const QCommandLineOption optProfi(QStringList() << "profile" << "p",
114 QApplication::translate("SimulatorMain", "Radio profile ID or Name to use for simulator."),
115 QApplication::translate("SimulatorMain", "profile"));
117 const QCommandLineOption optRadio(QStringList() << "radio" << "r",
118 QApplication::translate("SimulatorMain", "Radio type to simulate (usually defined in profile)."),
119 QApplication::translate("SimulatorMain", "radio"));
121 const QCommandLineOption optSdDir(QStringList() << "sd-path" << "s",
122 QApplication::translate("SimulatorMain", "Directory containing the SD card image to use. The default is configured in the chosen Radio Profile."),
123 QApplication::translate("SimulatorMain", "path"));
125 const QCommandLineOption optStart(QStringList() << "start-with" << "w",
126 QApplication::translate("SimulatorMain", "Data source type to use (applicable to Horus only). One of:") + " (file|folder|sd)",
127 QApplication::translate("SimulatorMain", "type"));
129 cliOptions.addPositionalArgument(QApplication::translate("SimulatorMain", "data-source"),
130 QApplication::translate("SimulatorMain", "Radio data (.bin/.eeprom/.otx) image file to use OR data folder path (for Horus-style radios).\n"
131 "NOTE: any existing EEPROM data incompatible with the selected radio type may be overwritten!"),
132 QApplication::translate("SimulatorMain", "[data-source]"));
134 cliOptions.addOption(optProfi);
135 cliOptions.addOption(optRadio);
136 cliOptions.addOption(optSdDir);
137 cliOptions.addOption(optStart);
139 QStringList args = QCoreApplication::arguments();
140 #ifdef Q_OS_WIN
141 // For backwards compat. with QxtCommandOptions, convert Windows-style CLI switches (/opt) since QCommandLineParser doesn't support them
142 for (int i=0; i < args.size(); ++i) {
143 args[i].replace(QRegExp("^/([^\\s]{2,10})$"), "--\\1"); // long opts
144 args[i].replace(QRegExp("^/([^\\s]){1}$"), "-\\1"); // short opts
146 #endif
148 if (!cliOptions.parse(args)) {
149 showHelp(cliOptions, cliOptions.errorText());
150 return CommandLineExitErr;
153 if (cliOptions.isSet(optHelp)) {
154 showHelp(cliOptions);
155 return CommandLineExitOk;
158 if (cliOptions.isSet(optVer)) {
159 showMessage(APP_SIMULATOR % " v" VERSION " " __DATE__, QMessageBox::Information);
160 return CommandLineExitOk;
163 if (cliOptions.isSet(optProfi)) {
164 bool chk;
165 pId = cliOptions.value(optProfi).toInt(&chk);
166 if (!chk)
167 pId = g.getActiveProfiles().key(cliOptions.value(optProfi), -1);
169 if (!g.getActiveProfiles().contains(pId)) {
170 showHelp(cliOptions, QApplication::translate("SimulatorMain", "Error: Profile ID %1 was not found.").arg(pId));
171 return CommandLineExitErr;
174 *simOptions = g.profile[pId].simulatorOptions();
175 cliOptsFound = true;
178 if (cliOptions.isSet(optRadio)) {
179 simOptions->firmwareId = cliOptions.value(optRadio);
180 cliOptsFound = true;
183 if (cliOptsFound || (simOptions->dataFile.isEmpty() && !simOptions->firmwareId.isEmpty())) {
184 // this constructs a new default radio data file name in the user-configured eeprom directory
185 simOptions->dataFile = SimulatorStartupDialog::radioEepromFileName(simOptions->firmwareId, g.eepromDir());
188 if (cliOptions.isSet(optSdDir)) {
189 simOptions->sdPath = cliOptions.value(optSdDir);
190 cliOptsFound = true;
193 if (cliOptions.positionalArguments().size()) {
194 QString datasrc = cliOptions.positionalArguments().at(0);
195 if (datasrc.contains(QRegExp(".*\\.[\\w]{2,6}$"))) {
196 simOptions->dataFile = datasrc;
197 simOptions->startupDataType = SimulatorOptions::START_WITH_FILE;
199 else {
200 simOptions->dataFolder = datasrc;
201 simOptions->startupDataType = SimulatorOptions::START_WITH_FOLDER;
203 cliOptsFound = true;
206 if (cliOptions.isSet(optStart)) {
207 QString stTyp = cliOptions.value(optStart);
208 if (stTyp == "file") {
209 simOptions->startupDataType = SimulatorOptions::START_WITH_FILE;
211 else if (stTyp == "folder") {
212 simOptions->startupDataType = SimulatorOptions::START_WITH_FOLDER;
214 else if (stTyp == "sd") {
215 simOptions->startupDataType = SimulatorOptions::START_WITH_SDPATH;
217 else {
218 showHelp(cliOptions, QApplication::translate("SimulatorMain", "Unrecognized startup data source type: %1").arg(stTyp));
219 return CommandLineExitErr;
222 cliOptsFound = true;
225 *profileId = pId;
226 if (cliOptsFound)
227 return CommandLineFound;
228 else
229 return CommandLineNone;
232 int main(int argc, char *argv[])
235 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
236 /* From doc: This attribute must be set before Q(Gui)Application is constructed. */
237 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
238 #endif
240 QApplication app(argc, argv);
241 app.setApplicationName(APP_SIMULATOR);
242 app.setApplicationVersion(VERSION);
243 app.setOrganizationName(COMPANY);
244 app.setOrganizationDomain(COMPANY_DOMAIN);
246 Q_INIT_RESOURCE(companion);
248 if (AppDebugMessageHandler::instance())
249 AppDebugMessageHandler::instance()->installAppMessageHandler();
251 CustomDebug::setFilterRules();
253 g.init(); // init settings before installing translations
255 if (AppDebugMessageHandler::instance() && g.appDebugLog() && !g.appLogsDir().isEmpty() && QDir().mkpath(g.appLogsDir())) {
256 QString fn = g.appLogsDir() % "/SimulatorDebug_" % QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss") % ".log";
257 simuDbgLog.setFileName(fn);
258 if (simuDbgLog.open(QIODevice::WriteOnly | QIODevice::Text)) {
259 AppDebugMessageHandler::instance()->addOutputDevice(&simuDbgLog);
263 Translations::installTranslators();
265 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
266 uint32_t sdlFlags = 0;
267 #ifdef JOYSTICKS
268 sdlFlags |= SDL_INIT_JOYSTICK;
269 #endif
270 #ifdef SIMU_AUDIO
271 sdlFlags |= SDL_INIT_AUDIO;
272 #endif
273 if (SDL_Init(sdlFlags) < 0) {
274 showMessage(QApplication::translate("SimulatorMain", "WARNING: couldn't initialize SDL:\n%1").arg(SDL_GetError()), QMessageBox::Warning);
276 #endif
278 registerStorageFactories();
279 registerOpenTxFirmwares();
280 SimulatorLoader::registerSimulators();
282 if (!SimulatorLoader::getAvailableSimulators().size()) {
283 showMessage(QApplication::translate("SimulatorMain", "ERROR: No simulator libraries available."), QMessageBox::Critical);
284 return finish(3);
287 int profileId = (g.simuLastProfId() > -1 ? g.simuLastProfId() : g.id());
288 SimulatorOptions simOptions = g.profile[profileId].simulatorOptions();
290 // TODO : defaults should be set in Profile::init()
291 if (simOptions.firmwareId.isEmpty())
292 simOptions.firmwareId = SimulatorLoader::findSimulatorByFirmwareName(g.profile[profileId].fwType());
293 if (simOptions.dataFolder.isEmpty())
294 simOptions.dataFolder = g.eepromDir();
295 if (simOptions.sdPath.isEmpty())
296 simOptions.sdPath = g.profile[profileId].sdPath();
298 // Handle startup options
300 // check for command-line options
301 CommandLineParseResult cliResult = cliOptions(&simOptions, &profileId);
303 if (cliResult == CommandLineExitOk)
304 return finish(0);
305 if (cliResult == CommandLineExitErr)
306 return finish(1);
308 // Present GUI startup options dialog if necessary
309 if (cliResult == CommandLineNone || profileId == -1 || simOptions.firmwareId.isEmpty() || (simOptions.dataFile.isEmpty() && simOptions.dataFolder.isEmpty())) {
310 SimulatorStartupDialog * dlg = new SimulatorStartupDialog(&simOptions, &profileId);
311 int ret = dlg->exec();
312 delete dlg;
313 if (ret != QDialog::Accepted) {
314 return finish(0);
317 qDebug() << "Starting with options: profileId=" << profileId << simOptions;
319 // Validate startup options
321 QString resultMsg;
322 if (profileId < 0 || simOptions.firmwareId.isEmpty() || (simOptions.dataFile.isEmpty() && simOptions.dataFolder.isEmpty())) {
323 resultMsg = QApplication::translate("SimulatorMain", "ERROR: Couldn't start simulator, missing radio/profile/data file/folder.\n Profile ID: [%1]; Radio ID: [%2];\nData File: [%3]");
324 showMessage(resultMsg.arg(profileId).arg(simOptions.firmwareId, simOptions.dataFile), QMessageBox::Critical);
325 return finish(1);
327 if (!g.getActiveProfiles().contains(profileId) || !SimulatorLoader::getAvailableSimulators().contains(simOptions.firmwareId)) {
328 QTextStream stream(&resultMsg);
329 stream << QApplication::translate("SimulatorMain", "ERROR: Radio profile or simulator firmware not found.\nProfile ID: [%1]; Radio ID: [%2]").arg(profileId).arg(simOptions.firmwareId);
330 stream << sharedHelpText();
331 showMessage(resultMsg, QMessageBox::Critical);
332 return finish(1);
335 // All checks passed, save profile ID and start simulator
337 g.sessionId(profileId);
338 g.simuLastProfId(profileId);
340 // Set global firmware environment
341 Firmware::setCurrentVariant(Firmware::getFirmwareForId(simOptions.firmwareId));
343 int result = 0;
344 SimulatorMainWindow * mainWindow = new SimulatorMainWindow(NULL, simOptions.firmwareId, SIMULATOR_FLAGS_STANDALONE);
345 if ((result = mainWindow->getExitStatus(&resultMsg))) {
346 if (resultMsg.isEmpty())
347 resultMsg = QApplication::translate("SimulatorMain", "Uknown error during Simulator startup.");
348 showMessage(resultMsg, QMessageBox::Critical);
350 else if (mainWindow->setOptions(simOptions, true)) {
351 mainWindow->show();
352 result = app.exec();
353 if (!result) {
354 if ((result = mainWindow->getExitStatus(&resultMsg)) && !resultMsg.isEmpty())
355 qWarning() << "Exit message from SimulatorMainWindow:" << resultMsg;
358 else {
359 result = 3;
362 delete mainWindow;
363 return finish(result);
366 int finish(int exitCode)
368 SimulatorLoader::unregisterSimulators();
369 unregisterOpenTxFirmwares();
370 unregisterStorageFactories();
372 #if defined(JOYSTICKS) || defined(SIMU_AUDIO)
373 SDL_Quit();
374 #endif
376 qDebug() << "SIMULATOR EXIT" << exitCode;
377 if (simuDbgLog.isOpen()) {
378 AppDebugMessageHandler::instance()->removeOutputDevice(&simuDbgLog);
379 simuDbgLog.close();
381 return exitCode;