From 6963097588817aede950eca2343d228655d67988 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 29 May 2014 11:31:04 -0700 Subject: [PATCH] commit --- .../flightgear/Aircraft/EasyStar/easystar.xml | 4 +- src/comm/QGCFlightGearLink.cc | 101 ++++++++++++--- src/comm/QGCFlightGearLink.h | 15 +-- src/qgcunittest/FlightGearTest.cc | 85 +++++++++++++ src/qgcunittest/FlightGearTest.h | 58 +++++++++ src/ui/QGCHilConfiguration.ui | 4 +- src/ui/QGCHilFlightGearConfiguration.cc | 119 +++++++++++++++--- src/ui/QGCHilFlightGearConfiguration.h | 23 +++- src/ui/QGCHilFlightGearConfiguration.ui | 2 +- 9 files changed, 361 insertions(+), 50 deletions(-) create mode 100644 src/qgcunittest/FlightGearTest.cc create mode 100644 src/qgcunittest/FlightGearTest.h diff --git a/files/flightgear/Aircraft/EasyStar/easystar.xml b/files/flightgear/Aircraft/EasyStar/easystar.xml index 3477038fe..742c6cb63 100644 --- a/files/flightgear/Aircraft/EasyStar/easystar.xml +++ b/files/flightgear/Aircraft/EasyStar/easystar.xml @@ -91,7 +91,7 @@ 4.0 0 NONE - FIXED + 0 @@ -107,7 +107,7 @@ 4.0 0 NONE - FIXED + 0 diff --git a/src/comm/QGCFlightGearLink.cc b/src/comm/QGCFlightGearLink.cc index d0164958a..ac1539c92 100644 --- a/src/comm/QGCFlightGearLink.cc +++ b/src/comm/QGCFlightGearLink.cc @@ -40,11 +40,14 @@ This file is part of the QGROUNDCONTROL project #include #include "MainWindow.h" +// FlightGear process start and connection is quite fragile. Uncomment the define below to get higher level of debug output +// for tracking down problems. +#define DEBUG_FLIGHTGEAR_CONNECT + QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments, QString remoteHost, QHostAddress host, quint16 port) : socket(NULL), process(NULL), - terraSync(NULL), - flightGearVersion(0), + flightGearVersion(3), startupArguments(startupArguments), _sensorHilEnabled(true), barometerOffsetkPa(0.0f) @@ -58,7 +61,7 @@ QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments this->connectState = false; this->currentPort = 49000+mav->getUASID(); this->mav = mav; - this->name = tr("FlightGear Link (port:%1)").arg(port); + this->name = tr("FlightGear 3.0+ Link (port:%1)").arg(port); setRemoteHost(remoteHost); } @@ -74,6 +77,7 @@ QGCFlightGearLink::~QGCFlightGearLink() * @brief Runs the thread * **/ + void QGCFlightGearLink::run() { qDebug() << "STARTING FLIGHTGEAR LINK"; @@ -305,23 +309,23 @@ void QGCFlightGearLink::processError(QProcess::ProcessError err) switch(err) { case QProcess::FailedToStart: - MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Failed to Start"), tr("Please check if the path and command is correct")); + MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("Please check if the path and command is correct")); break; case QProcess::Crashed: - MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear")); + MainWindow::instance()->showCriticalMessage(tr("FlightGear Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear")); break; case QProcess::Timedout: - MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Start Timed Out"), tr("Please check if the path and command is correct")); + MainWindow::instance()->showCriticalMessage(tr("FlightGear Start Timed Out"), tr("Please check if the path and command is correct")); break; case QProcess::WriteError: - MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct")); + MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct")); break; case QProcess::ReadError: - MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct")); + MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct")); break; case QProcess::UnknownError: default: - MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Error"), tr("Please check if the path and command is correct.")); + MainWindow::instance()->showCriticalMessage(tr("FlightGear Error"), tr("Please check if the path and command is correct.")); break; } } @@ -633,12 +637,6 @@ bool QGCFlightGearLink::disconnectSimulation() delete process; process = NULL; } - if (terraSync) - { - terraSync->close(); - delete terraSync; - terraSync = NULL; - } if (socket) { socket->close(); @@ -653,6 +651,79 @@ bool QGCFlightGearLink::disconnectSimulation() return !connectState; } +/// @brief Splits a space seperated set of command line arguments into a QStringList. +/// Quoted strings are allowed and handled correctly. +/// @param uiArgs Arguments to parse +/// @param argList Returned argument list +/// @return Returns false if the argument list has mistmatced quotes within in. + +bool QGCFlightGearLink::parseUIArguments(QString uiArgs, QStringList& argList) +{ + + // This is not as easy as it seams since some options can be quoted to preserve spaces within things like + // directories. There is likely some crazed regular expression which can do this. But after trying that + // route I gave up and instead here is the code which does it the hard way. Another thing to be aware of + // is that the QStringList passed to QProces::start is similar to what you would get in argv after the + // command line is processed. This means that quoted strings have the quotes removed before making it to argv. + + bool inQuotedString = false; + bool previousSpace = false; + QString currentArg; + for (int i=0; ishowCriticalMessage(tr("FlightGear Failed to Start"), tr("Mismatched quotes in specified command line options")); + return false; + } + if (!currentArg.isEmpty()) { + argList += currentArg; + currentArg.clear(); + } + } + } + } else if (chr == '\"') { + // Flip the state of being in a quoted string. Note that we specifically do not add the + // quote to the string. This replicates standards command line parsing behaviour. + if (chr == '\"') { + inQuotedString = !inQuotedString; + } + previousSpace = false; + } else { + // Flip the state of being in a quoted string + if (chr == '\"') { + inQuotedString = !inQuotedString; + } + previousSpace = false; + currentArg += chr; + } + } + // We should never end parsing on an unterminated quote + if (inQuotedString) { + return false; + } + + // Finish up last arg + if (!currentArg.isEmpty()) { + argList += currentArg; + currentArg.clear(); + } + + return true; +} + /** * @brief Connect the connection. * diff --git a/src/comm/QGCFlightGearLink.h b/src/comm/QGCFlightGearLink.h index 603286b93..3409d312c 100644 --- a/src/comm/QGCFlightGearLink.h +++ b/src/comm/QGCFlightGearLink.h @@ -87,6 +87,8 @@ public: void sensorHilEnabled(bool sensorHilEnabled) { _sensorHilEnabled = sensorHilEnabled; } + + static bool parseUIArguments(QString uiArgs, QStringList& argList); void run(); @@ -130,8 +132,6 @@ public slots: bool connectSimulation(); bool disconnectSimulation(); - void printTerraSyncOutput(); - void printTerraSyncError(); void printFgfsOutput(); void printFgfsError(); void setStartupArguments(QString startupArguments); @@ -147,19 +147,8 @@ protected: QUdpSocket* socket; bool connectState; - quint64 bitsSentTotal; - quint64 bitsSentCurrent; - quint64 bitsSentMax; - quint64 bitsReceivedTotal; - quint64 bitsReceivedCurrent; - quint64 bitsReceivedMax; - quint64 connectionStartTime; - QMutex statisticsMutex; - QMutex dataMutex; - QTimer refreshTimer; UASInterface* mav; QProcess* process; - QProcess* terraSync; unsigned int flightGearVersion; QString startupArguments; bool _sensorHilEnabled; diff --git a/src/qgcunittest/FlightGearTest.cc b/src/qgcunittest/FlightGearTest.cc new file mode 100644 index 000000000..28b970501 --- /dev/null +++ b/src/qgcunittest/FlightGearTest.cc @@ -0,0 +1,85 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL 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. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include "FlightGearTest.h" +#include "QGCFlightGearLink.h" + +/// @file +/// @brief FlightGearUnitTest HIL Simulation class +/// +/// @author Don Gagne + +FlightGearUnitTest::FlightGearUnitTest(void) +{ + +} + +// Called before every test +void FlightGearUnitTest::init(void) +{ + // Nothing here yet +} + +// Called after every test +void FlightGearUnitTest::cleanup(void) +{ + // Nothing here yet +} + +/// @brief The QGCFlightGearLink::parseUIArguments method is fairly involved so we have a unit +// test for it. +void FlightGearUnitTest::_parseUIArguments_test(void) +{ + typedef struct { + const char* args; + const char* expectedListAsChar; + bool expectedReturn; + } ParseUIArgumentsTestCase_t; + + static const ParseUIArgumentsTestCase_t rgTestCases[] = { + { "foo", "foo", true }, + { "foo bar", "foo|bar", true }, + { "--foo --bar", "--foo|--bar", true }, + { "foo=bar", "foo=bar", true }, + { "foo=bar bar=baz", "foo=bar|bar=baz", true }, + { "foo=\"bar\"", "foo=bar", true }, + { "foo=\"bar\" bar=\"baz\"", "foo=bar|bar=baz", true }, + { "foo=\"b ar\"", "foo=b ar", true }, + { "foo=\"b ar\" bar=\"b az\"", "foo=b ar|bar=b az", true }, + { "foo\"", NULL, false }, + }; + + for (size_t i=0; iargs, returnedArgList); + QCOMPARE(actualReturn, testCase->expectedReturn); + if (actualReturn) { + QStringList expectedArgList = QString(testCase->expectedListAsChar).split("|"); + if (returnedArgList != expectedArgList) { + qDebug() << "About to fail: Returned" << returnedArgList << "Expected" << expectedArgList; + } + QVERIFY(returnedArgList == expectedArgList); + } + } +} diff --git a/src/qgcunittest/FlightGearTest.h b/src/qgcunittest/FlightGearTest.h new file mode 100644 index 000000000..898006c69 --- /dev/null +++ b/src/qgcunittest/FlightGearTest.h @@ -0,0 +1,58 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL 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. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#ifndef TCPLINKTEST_H +#define TCPLINKTEST_H + +#include +#include +#include + +#include "AutoTest.h" +#include "TCPLink.h" +#include "MultiSignalSpy.h" + +/// @file +/// @brief FlightGear HIL Simulation unit tests +/// +/// @author Don Gagne + +class FlightGearUnitTest : public QObject +{ + Q_OBJECT + +public: + FlightGearUnitTest(void); + +private slots: + void init(void); + void cleanup(void); + + void _parseUIArguments_test(void); + +private: +}; + +DECLARE_TEST(FlightGearUnitTest) + +#endif diff --git a/src/ui/QGCHilConfiguration.ui b/src/ui/QGCHilConfiguration.ui index 1db3ec1b6..8da9ad667 100644 --- a/src/ui/QGCHilConfiguration.ui +++ b/src/ui/QGCHilConfiguration.ui @@ -39,7 +39,7 @@ - Flightgear + FlightGear 3.0+ @@ -69,7 +69,7 @@ - No simulator active.. + diff --git a/src/ui/QGCHilFlightGearConfiguration.cc b/src/ui/QGCHilFlightGearConfiguration.cc index 67f4eb21d..f1c5efea3 100644 --- a/src/ui/QGCHilFlightGearConfiguration.cc +++ b/src/ui/QGCHilFlightGearConfiguration.cc @@ -1,51 +1,127 @@ #include "QGCHilFlightGearConfiguration.h" -#include "ui_QGCHilFlightGearConfiguration.h" #include "MainWindow.h" -QGCHilFlightGearConfiguration::QGCHilFlightGearConfiguration(UAS* mav,QWidget *parent) : +// Various settings groups and keys +const char* QGCHilFlightGearConfiguration::_settingsGroup = "QGC_HILCONFIG_FLIGHTGEAR"; +const char* QGCHilFlightGearConfiguration::_mavSettingsSubGroupFixedWing = "FIXED_WING"; +const char* QGCHilFlightGearConfiguration::_mavSettingsSubGroupQuadRotor = "QUADROTOR"; +const char* QGCHilFlightGearConfiguration::_aircraftKey = "AIRCRAFT"; +const char* QGCHilFlightGearConfiguration::_optionsKey = "OPTIONS"; +const char* QGCHilFlightGearConfiguration::_barometerOffsetKey = "BARO_OFFSET"; +const char* QGCHilFlightGearConfiguration::_sensorHilKey = "SENSOR_HIL"; + +// Default set of optional command line parameters. If FlightGear won't run HIL without it it should go into +// the QGCFlightGearLink code instead. +const char* QGCHilFlightGearConfiguration::_defaultOptions = "--roll=0 --pitch=0 --vc=0 --heading=300 --timeofday=noon --disable-hud-3d --disable-fullscreen --geometry=400x300 --disable-anti-alias-hud --wind=0@0 --turbulence=0.0 --prop:/sim/frame-rate-throttle-hz=30 --control=mouse --disable-sound --disable-random-objects --disable-ai-traffic --shading-flat --fog-disable --disable-specular-highlight --disable-random-objects --disable-panel --disable-clouds --fdm=jsb --units-meters --enable-terrasync --atlas=socket,out,1,localhost,5505,udp"; + +QGCHilFlightGearConfiguration::QGCHilFlightGearConfiguration(UAS* mav, QWidget *parent) : QWidget(parent), - mav(mav), - ui(new Ui::QGCHilFlightGearConfiguration) -{ - ui->setupUi(this); + _mav(mav), + _mavSettingsSubGroup(NULL), + _resetOptionsAction(tr("Reset to default options"), this) - QStringList items = QStringList(); - if (mav->getSystemType() == MAV_TYPE_FIXED_WING) +{ + Q_ASSERT(_mav); + + _ui.setupUi(this); + + QStringList items; + if (_mav->getSystemType() == MAV_TYPE_FIXED_WING) { items << "EasyStar"; items << "Rascal110-JSBSim"; items << "c172p"; items << "YardStik"; items << "Malolo1"; + _mavSettingsSubGroup = _mavSettingsSubGroupFixedWing; } - else if (mav->getSystemType() == MAV_TYPE_QUADROTOR) + else if (_mav->getSystemType() == MAV_TYPE_QUADROTOR) { items << "arducopter"; + _mavSettingsSubGroup = _mavSettingsSubGroupQuadRotor; } else { + // FIXME: Should disable all input, won't work. Show error message in the status label thing. items << ""; } - ui->aircraftComboBox->addItems(items); + _ui.aircraftComboBox->addItems(items); + + QSettings settings; + settings.beginGroup(_settingsGroup); + settings.beginGroup(_mavSettingsSubGroup); + QString aircraft = settings.value(_aircraftKey).toString(); + QString options = settings.value(_optionsKey).toString(); + QString baroOffset = settings.value(_barometerOffsetKey).toString(); + bool sensorHil = settings.value(_sensorHilKey, QVariant(true)).toBool(); + settings.endGroup(); + settings.endGroup(); + + if (!aircraft.isEmpty()) { + int index = _ui.aircraftComboBox->findText(aircraft); + if (index != -1) { + _ui.aircraftComboBox->setCurrentIndex(index); + } + } + if (options.isEmpty()) { + options = _defaultOptions; + } + _ui.optionsPlainTextEdit->setPlainText(options); + _ui.barometerOffsetLineEdit->setText(baroOffset); + _ui.sensorHilCheckBox->setChecked(sensorHil); + + // Provide an option on the context menu to reset the option back to default + _ui.optionsPlainTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); + bool success = connect(&_resetOptionsAction, SIGNAL(triggered()), this, SLOT(_setDefaultOptions())); + Q_ASSERT(success); + success = connect(_ui.optionsPlainTextEdit, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(_showContextMenu(const QPoint &))); + Q_ASSERT(success); } QGCHilFlightGearConfiguration::~QGCHilFlightGearConfiguration() { - delete ui; + QString aircraft = _ui.aircraftComboBox->currentText(); + QString options = _ui.optionsPlainTextEdit->toPlainText(); + QString baroOffset = _ui.barometerOffsetLineEdit->text(); + bool sensorHil = _ui.sensorHilCheckBox->isChecked(); + + QSettings settings; + settings.beginGroup(_settingsGroup); + settings.beginGroup(_mavSettingsSubGroup); + + if (aircraft.isEmpty()) { + settings.remove(_aircraftKey); + } else { + settings.setValue(_aircraftKey, aircraft); + } + + if (options.isEmpty() || options == _defaultOptions) { + settings.remove(_optionsKey); + } else { + settings.setValue(_optionsKey, options); + } + + // Double QVariant is to convert from string to float. It will change to 0.0 if invalid. + settings.setValue(_barometerOffsetKey, QVariant(QVariant(baroOffset).toFloat())); + + settings.setValue(_sensorHilKey, QVariant(sensorHil)); + + settings.endGroup(); + settings.endGroup(); } void QGCHilFlightGearConfiguration::on_startButton_clicked() { //XXX check validity of inputs - QString options = ui->optionsPlainTextEdit->toPlainText(); - options.append(" --aircraft=" + ui->aircraftComboBox->currentText()); - mav->enableHilFlightGear(true, options, ui->sensorHilCheckBox->isChecked(), this); + QString options = _ui.optionsPlainTextEdit->toPlainText(); + options.append(" --aircraft=" + _ui.aircraftComboBox->currentText()); + _mav->enableHilFlightGear(true, options, _ui.sensorHilCheckBox->isChecked(), this); } void QGCHilFlightGearConfiguration::on_stopButton_clicked() { - mav->stopHil(); + _mav->stopHil(); } void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const QString& baroOffset) @@ -53,3 +129,16 @@ void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const emit barometerOffsetChanged(baroOffset.toFloat()); } +void QGCHilFlightGearConfiguration::_showContextMenu(const QPoint &pt) +{ + QMenu* menu = _ui.optionsPlainTextEdit->createStandardContextMenu(); + menu->addAction(&_resetOptionsAction); + menu->exec(_ui.optionsPlainTextEdit->mapToGlobal(pt)); + delete menu; +} + +void QGCHilFlightGearConfiguration::_setDefaultOptions(void) +{ + _ui.optionsPlainTextEdit->setPlainText(_defaultOptions); +} + diff --git a/src/ui/QGCHilFlightGearConfiguration.h b/src/ui/QGCHilFlightGearConfiguration.h index 7e0e48901..0b72996c6 100644 --- a/src/ui/QGCHilFlightGearConfiguration.h +++ b/src/ui/QGCHilFlightGearConfiguration.h @@ -7,6 +7,8 @@ #include "QGCFlightGearLink.h" #include "UAS.h" +#include "ui_QGCHilFlightGearConfiguration.h" + namespace Ui { class QGCHilFlightGearConfiguration; } @@ -20,16 +22,33 @@ public: ~QGCHilFlightGearConfiguration(); protected: - UAS* mav; private slots: void on_startButton_clicked(); void on_stopButton_clicked(); void on_barometerOffsetLineEdit_textChanged(const QString& baroOffset); + void _setDefaultOptions(void); + void _showContextMenu(const QPoint& pt); private: - Ui::QGCHilFlightGearConfiguration *ui; + Ui::QGCHilFlightGearConfiguration _ui; + UAS* _mav; /// mav associated with this ui + + static const char* _settingsGroup; /// Top level settings group + const char* _mavSettingsSubGroup; /// We maintain a settings sub group per mav type + + static const char* _mavSettingsSubGroupFixedWing; /// Subgroup if mav type is MAV_TYPE_FIXED_WING + static const char* _mavSettingsSubGroupQuadRotor; /// Subgroup is mav type is MAV_TYPE_QUADROTOR + static const char* _aircraftKey; /// Settings key for aircraft selection + static const char* _optionsKey; /// Settings key for FlightGear cmd line options + static const char* _barometerOffsetKey; /// Settings key for barometer offset + static const char* _sensorHilKey; /// Settings key for Sensor Hil checkbox + + static const char* _defaultOptions; /// Default set of FlightGEar command line options + + QAction _resetOptionsAction; /// Context menu item to reset options to default + signals: void barometerOffsetChanged(float barometerOffsetkPa); }; diff --git a/src/ui/QGCHilFlightGearConfiguration.ui b/src/ui/QGCHilFlightGearConfiguration.ui index c91f992ac..96bb40fca 100644 --- a/src/ui/QGCHilFlightGearConfiguration.ui +++ b/src/ui/QGCHilFlightGearConfiguration.ui @@ -68,7 +68,7 @@ - --roll=0 --pitch=0 --vc=0 --heading=300 --timeofday=noon --disable-hud-3d --disable-fullscreen --geometry=400x300 --disable-anti-alias-hud --wind=0@0 --turbulence=0.0 --prop:/sim/frame-rate-throttle-hz=30 --control=mouse --disable-sound --disable-random-objects --disable-ai-models --shading-flat --fog-disable --disable-specular-highlight --disable-random-objects --disable-panel --disable-clouds --fdm=jsb --units-meters --prop:/engines/engine/running=true + -- 2.22.0