Commit ff33c79e authored by Lorenz Meier's avatar Lorenz Meier

Merged FlightGear

parents c7e4a65c e3432de0
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
<damping_coeff_rebound unit="LBS/FT/SEC"> 4.0 </damping_coeff_rebound> <damping_coeff_rebound unit="LBS/FT/SEC"> 4.0 </damping_coeff_rebound>
<max_steer unit="DEG">0</max_steer> <max_steer unit="DEG">0</max_steer>
<brake_group>NONE</brake_group> <brake_group>NONE</brake_group>
<retractable>FIXED</retractable> <retractable>0</retractable>
</contact> </contact>
<contact type="BOGEY" name="NOSE"> <contact type="BOGEY" name="NOSE">
<location unit="M"> <location unit="M">
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
<damping_coeff_rebound unit="LBS/FT/SEC"> 4.0 </damping_coeff_rebound> <damping_coeff_rebound unit="LBS/FT/SEC"> 4.0 </damping_coeff_rebound>
<max_steer unit="DEG">0</max_steer> <max_steer unit="DEG">0</max_steer>
<brake_group>NONE</brake_group> <brake_group>NONE</brake_group>
<retractable>FIXED</retractable> <retractable>0</retractable>
</contact> </contact>
<contact type="STRUCTURE" name="TAIL"> <contact type="STRUCTURE" name="TAIL">
......
...@@ -187,7 +187,8 @@ DebugBuild { ...@@ -187,7 +187,8 @@ DebugBuild {
src/qgcunittest/MockUAS.h \ src/qgcunittest/MockUAS.h \
src/qgcunittest/MockQGCUASParamManager.h \ src/qgcunittest/MockQGCUASParamManager.h \
src/qgcunittest/MultiSignalSpy.h \ src/qgcunittest/MultiSignalSpy.h \
src/qgcunittest/FlightModeConfigTest.h src/qgcunittest/FlightModeConfigTest.h \
src/qgcunittest/FlightGearTest.h
SOURCES += \ SOURCES += \
src/qgcunittest/UASUnitTest.cc \ src/qgcunittest/UASUnitTest.cc \
...@@ -195,7 +196,8 @@ DebugBuild { ...@@ -195,7 +196,8 @@ DebugBuild {
src/qgcunittest/MockUAS.cc \ src/qgcunittest/MockUAS.cc \
src/qgcunittest/MockQGCUASParamManager.cc \ src/qgcunittest/MockQGCUASParamManager.cc \
src/qgcunittest/MultiSignalSpy.cc \ src/qgcunittest/MultiSignalSpy.cc \
src/qgcunittest/FlightModeConfigTest.cc src/qgcunittest/FlightModeConfigTest.cc \
src/qgcunittest/FlightGearTest.cc
} }
# #
......
...@@ -40,14 +40,17 @@ This file is part of the QGROUNDCONTROL project ...@@ -40,14 +40,17 @@ This file is part of the QGROUNDCONTROL project
#include <QHostInfo> #include <QHostInfo>
#include "MainWindow.h" #include "MainWindow.h"
// FlightGear _fgProcess 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) : QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments, QString remoteHost, QHostAddress host, quint16 port) :
socket(NULL), flightGearVersion(3),
process(NULL),
terraSync(NULL),
flightGearVersion(0),
startupArguments(startupArguments), startupArguments(startupArguments),
_sensorHilEnabled(true), _sensorHilEnabled(true),
barometerOffsetkPa(0.0f) barometerOffsetkPa(0.0f),
_udpCommSocket(NULL),
_fgProcess(NULL)
{ {
// We're doing it wrong - because the Qt folks got the API wrong: // We're doing it wrong - because the Qt folks got the API wrong:
// http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/ // http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/
...@@ -58,237 +61,78 @@ QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments ...@@ -58,237 +61,78 @@ QGCFlightGearLink::QGCFlightGearLink(UASInterface* mav, QString startupArguments
this->connectState = false; this->connectState = false;
this->currentPort = 49000+mav->getUASID(); this->currentPort = 49000+mav->getUASID();
this->mav = mav; 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); setRemoteHost(remoteHost);
// We need a mechanism so show error message from our FGLink thread on the UI thread. This signal connection will do that for us.
connect(this, SIGNAL(showCriticalMessageFromThread(const QString&, const QString&)), MainWindow::instance(), SLOT(showCriticalMessage(const QString&, const QString&)));
} }
QGCFlightGearLink::~QGCFlightGearLink() QGCFlightGearLink::~QGCFlightGearLink()
{ //do not disconnect unless it is connected. { //do not disconnect unless it is connected.
//disconnectSimulation will delete the memory that was allocated for proces, terraSync and socket //disconnectSimulation will delete the memory that was allocated for proces, terraSync and _udpCommSocket
if(connectState){ if(connectState){
disconnectSimulation(); disconnectSimulation();
} }
} }
/** /// @brief Runs the simulation thread. We do setup work here which needs to happen in the seperate thread.
* @brief Runs the thread
*
**/
void QGCFlightGearLink::run() void QGCFlightGearLink::run()
{ {
qDebug() << "STARTING FLIGHTGEAR LINK"; Q_ASSERT(mav);
Q_ASSERT(!_fgProcessName.isEmpty());
if (!mav) return;
socket = new QUdpSocket(this); // We communicate with FlightGear over a UDP _udpCommSocket
socket->moveToThread(this); _udpCommSocket = new QUdpSocket(this);
connectState = socket->bind(host, port); Q_CHECK_PTR(_udpCommSocket);
_udpCommSocket->moveToThread(this);
QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readBytes())); _udpCommSocket->bind(host, port);
QObject::connect(_udpCommSocket, SIGNAL(readyRead()), this, SLOT(readBytes()));
process = new QProcess(this);
process->moveToThread(this);
terraSync = new QProcess(this); // Connect to the various HIL signals that we use to then send information across the UDP protocol to FlightGear.
terraSync->moveToThread(this); connect(mav, SIGNAL(hilControlsChanged(quint64, float, float, float, float, quint8, quint8)),
this, SLOT(updateControls(quint64,float,float,float,float,quint8,quint8)));
connect(mav, SIGNAL(hilControlsChanged(quint64, float, float, float, float, quint8, quint8)), this, SLOT(updateControls(quint64,float,float,float,float,quint8,quint8))); connect(this, SIGNAL(hilStateChanged(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)),
connect(this, SIGNAL(hilStateChanged(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)), mav, SLOT(sendHilState(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float))); mav, SLOT(sendHilState(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)));
connect(this, SIGNAL(sensorHilGpsChanged(quint64, double, double, double, int, float, float, float, float, float, float, float, int)), mav, SLOT(sendHilGps(quint64, double, double, double, int, float, float, float, float, float, float, float, int))); connect(this, SIGNAL(sensorHilGpsChanged(quint64, double, double, double, int, float, float, float, float, float, float, float, int)),
connect(this, SIGNAL(sensorHilRawImuChanged(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)), mav, SLOT(sendHilSensors(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32))); mav, SLOT(sendHilGps(quint64, double, double, double, int, float, float, float, float, float, float, float, int)));
connect(this, SIGNAL(sensorHilRawImuChanged(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)),
UAS* uas = dynamic_cast<UAS*>(mav); mav, SLOT(sendHilSensors(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)));
if (uas)
{ // Start a new QProcess to run FlightGear in
uas->startHil(); _fgProcess = new QProcess(this);
} Q_CHECK_PTR(_fgProcess);
_fgProcess->moveToThread(this);
//connect(&refreshTimer, SIGNAL(timeout()), this, SLOT(sendUAVUpdate()));
// Catch process error connect(_fgProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
QObject::connect( process, SIGNAL(error(QProcess::ProcessError)), #ifdef DEBUG_FLIGHTGEAR_CONNECT
this, SLOT(processError(QProcess::ProcessError))); connect(_fgProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(_printFgfsOutput()));
QObject::connect( terraSync, SIGNAL(error(QProcess::ProcessError)), connect(_fgProcess, SIGNAL(readyReadStandardError()), this, SLOT(_printFgfsError()));
this, SLOT(processError(QProcess::ProcessError)));
// Start Flightgear
QStringList flightGearArguments;
QString processFgfs;
QString processTerraSync;
QString fgRoot;
QString fgScenery;
QString terraSyncScenery;
QString fgAircraft;
#ifdef Q_OS_MACX
processFgfs = "/Applications/FlightGear.app/Contents/Resources/fgfs";
processTerraSync = "/Applications/FlightGear.app/Contents/Resources/terrasync";
//fgRoot = "/Applications/FlightGear.app/Contents/Resources/data";
fgScenery = "/Applications/FlightGear.app/Contents/Resources/data/Scenery";
terraSyncScenery = "/Applications/FlightGear.app/Contents/Resources/data/Scenery-TerraSync";
// /Applications/FlightGear.app/Contents/Resources/data/Scenery:
#endif
#ifdef Q_OS_WIN32
processFgfs = "C:\\Program Files (x86)\\FlightGear\\bin\\Win32\\fgfs";
//fgRoot = "C:\\Program Files (x86)\\FlightGear\\data";
fgScenery = "C:\\Program Files (x86)\\FlightGear\\data\\Scenery";
terraSyncScenery = "C:\\Program Files (x86)\\FlightGear\\data\\Scenery-Terrasync";
#endif #endif
#ifdef Q_OS_LINUX if (!_fgProcessWorkingDirPath.isEmpty()) {
processFgfs = "fgfs"; _fgProcess->setWorkingDirectory(_fgProcessWorkingDirPath);
//fgRoot = "/usr/share/flightgear"; qDebug() << "Working directory" << _fgProcess->workingDirectory();
QString fgScenery1 = "/usr/share/flightgear/data/Scenery";
QString fgScenery2 = "/usr/share/games/flightgear/Scenery"; // Ubuntu default location
fgScenery = ""; //Flightgear can also start with fgScenery = ""
if (QDir(fgScenery1).exists())
fgScenery = fgScenery1;
else if (QDir(fgScenery2).exists())
fgScenery = fgScenery2;
processTerraSync = "nice"; //according to http://wiki.flightgear.org/TerraSync, run with lower priority
terraSyncScenery = QDir::homePath() + "/.terrasync/Scenery"; //according to http://wiki.flightgear.org/TerraSync a separate directory is used
#endif
fgAircraft = QApplication::applicationDirPath() + "/files/flightgear/Aircraft";
// Sanity checks
bool sane = true;
// QFileInfo executable(processFgfs);
// if (!executable.isExecutable())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear was not found at %1").arg(processFgfs));
// sane = false;
// }
// QFileInfo root(fgRoot);
// if (!root.isDir())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear data directory was not found at %1").arg(fgRoot));
// sane = false;
// }
// QFileInfo scenery(fgScenery);
// if (!scenery.isDir())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("FlightGear scenery directory was not found at %1").arg(fgScenery));
// sane = false;
// }
// QFileInfo terraSyncExecutableInfo(processTerraSync);
// if (!terraSyncExecutableInfo.isExecutable())
// {
// MainWindow::instance()->showCriticalMessage(tr("FlightGear Failed to Start"), tr("TerraSync was not found at %1").arg(processTerraSync));
// sane = false;
// }
if (!sane) return;
// --atlas=socket,out,1,localhost,5505,udp
// terrasync -p 5505 -S -d /usr/local/share/TerraSync
/*Prepare FlightGear Arguments */
//flightGearArguments << QString("--fg-root=%1").arg(fgRoot);
flightGearArguments << QString("--fg-scenery=%1:%2").arg(fgScenery).arg(terraSyncScenery); //according to http://wiki.flightgear.org/TerraSync a separate directory is used
flightGearArguments << QString("--fg-aircraft=%1").arg(fgAircraft);
if (mav->getSystemType() == MAV_TYPE_QUADROTOR)
{
flightGearArguments << QString("--generic=socket,out,300,127.0.0.1,%1,udp,qgroundcontrol-quadrotor").arg(port);
flightGearArguments << QString("--generic=socket,in,300,127.0.0.1,%1,udp,qgroundcontrol-quadrotor").arg(currentPort);
}
else
{
flightGearArguments << QString("--generic=socket,out,300,127.0.0.1,%1,udp,qgroundcontrol-fixed-wing").arg(port);
flightGearArguments << QString("--generic=socket,in,300,127.0.0.1,%1,udp,qgroundcontrol-fixed-wing").arg(currentPort);
}
flightGearArguments << "--atlas=socket,out,1,localhost,5505,udp";
// flightGearArguments << "--in-air";
// flightGearArguments << "--roll=0";
// flightGearArguments << "--pitch=0";
// flightGearArguments << "--vc=90";
// flightGearArguments << "--heading=300";
// flightGearArguments << "--timeofday=noon";
// flightGearArguments << "--disable-hud-3d";
// flightGearArguments << "--disable-fullscreen";
// flightGearArguments << "--geometry=400x300";
// flightGearArguments << "--disable-anti-alias-hud";
// flightGearArguments << "--wind=0@0";
// flightGearArguments << "--turbulence=0.0";
// flightGearArguments << "--prop:/sim/frame-rate-throttle-hz=30";
// flightGearArguments << "--control=mouse";
// flightGearArguments << "--disable-intro-music";
// flightGearArguments << "--disable-sound";
// flightGearArguments << "--disable-random-objects";
// flightGearArguments << "--disable-ai-models";
// flightGearArguments << "--shading-flat";
// flightGearArguments << "--fog-disable";
// flightGearArguments << "--disable-specular-highlight";
// //flightGearArguments << "--disable-skyblend";
// flightGearArguments << "--disable-random-objects";
// flightGearArguments << "--disable-panel";
// //flightGearArguments << "--disable-horizon-effect";
// flightGearArguments << "--disable-clouds";
// flightGearArguments << "--fdm=jsb";
// flightGearArguments << "--units-meters"; //XXX: check: the protocol xml has already a conversion from feet to m?
// flightGearArguments << "--notrim";
flightGearArguments += startupArguments.split(" ");
if (mav->getSystemType() == MAV_TYPE_QUADROTOR)
{
// Start all engines of the quad
flightGearArguments << "--prop:/engines/engine[0]/running=true";
flightGearArguments << "--prop:/engines/engine[1]/running=true";
flightGearArguments << "--prop:/engines/engine[2]/running=true";
flightGearArguments << "--prop:/engines/engine[3]/running=true";
}
else
{
flightGearArguments << "--prop:/engines/engine/running=true";
} }
flightGearArguments << QString("--lat=%1").arg(UASManager::instance()->getHomeLatitude());
flightGearArguments << QString("--lon=%1").arg(UASManager::instance()->getHomeLongitude()); #ifdef Q_OS_WIN32
//The altitude is not set because an altitude not equal to the ground altitude leads to a non-zero default throttle in flightgear // On Windows we need to full qualify the location of the excecutable. The call to setWorkingDirectory only
//Without the altitude-setting the aircraft is positioned on the ground // sets the QProcess context, not the QProcess::start context. For some strange reason this is not the case on
//flightGearArguments << QString("--altitude=%1").arg(UASManager::instance()->getHomeAltitude()); // OSX.
QDir fgProcessFullyQualified(_fgProcessWorkingDirPath);
// Add new argument with this: flightGearArguments << ""; _fgProcessName = fgProcessFullyQualified.absoluteFilePath(_fgProcessName);
//flightGearArguments << QString("--aircraft=%2").arg(aircraft);
/*Prepare TerraSync Arguments */
QStringList terraSyncArguments;
#ifdef Q_OS_LINUX
terraSyncArguments << "terrasync";
#endif #endif
terraSyncArguments << "-p";
terraSyncArguments << "5505"; #ifdef DEBUG_FLIGHTGEAR_CONNECT
terraSyncArguments << "-S"; qDebug() << "\nStarting FlightGear" << _fgProcessWorkingDirPath << _fgProcessName << _fgArgList << "\n";
terraSyncArguments << "-d";
terraSyncArguments << terraSyncScenery; //according to http://wiki.flightgear.org/TerraSync a separate directory is used
#ifdef Q_OS_LINUX
/* Setting environment */
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
process->setProcessEnvironment(env);
terraSync->setProcessEnvironment(env);
#endif #endif
// connect (terraSync, SIGNAL(readyReadStandardOutput()), this, SLOT(printTerraSyncOutput()));
// connect (terraSync, SIGNAL(readyReadStandardError()), this, SLOT(printTerraSyncError())); _fgProcess->start(_fgProcessName, _fgArgList);
terraSync->start(processTerraSync, terraSyncArguments); connectState = true;
// qDebug() << "STARTING: " << processTerraSync << terraSyncArguments;
process->start(processFgfs, flightGearArguments);
// connect (process, SIGNAL(readyReadStandardOutput()), this, SLOT(printFgfsOutput()));
// connect (process, SIGNAL(readyReadStandardError()), this, SLOT(printFgfsError()));
emit simulationConnected(connectState); emit simulationConnected(connectState);
if (connectState) { emit simulationConnected();
emit simulationConnected();
connectionStartTime = QGC::groundTimeUsecs()/1000;
}
qDebug() << "STARTING SIM";
// qDebug() << "STARTING: " << processFgfs << flightGearArguments;
exec(); exec();
} }
...@@ -305,23 +149,23 @@ void QGCFlightGearLink::processError(QProcess::ProcessError err) ...@@ -305,23 +149,23 @@ void QGCFlightGearLink::processError(QProcess::ProcessError err)
switch(err) switch(err)
{ {
case QProcess::FailedToStart: case QProcess::FailedToStart:
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Failed to Start"), tr("Please check if the path and command is correct")); emit showCriticalMessageFromThread(tr("FlightGear Failed to Start"), _fgProcess->errorString());
break; break;
case QProcess::Crashed: case QProcess::Crashed:
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear")); emit showCriticalMessageFromThread(tr("FlightGear Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear"));
break; break;
case QProcess::Timedout: case QProcess::Timedout:
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Start Timed Out"), tr("Please check if the path and command is correct")); emit showCriticalMessageFromThread(tr("FlightGear Start Timed Out"), tr("Please check if the path and command is correct"));
break; break;
case QProcess::WriteError: case QProcess::WriteError:
MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct")); emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
break; break;
case QProcess::ReadError: case QProcess::ReadError:
MainWindow::instance()->showCriticalMessage(tr("Could not Communicate with FlightGear/TerraSync"), tr("Please check if the path and command is correct")); emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
break; break;
case QProcess::UnknownError: case QProcess::UnknownError:
default: default:
MainWindow::instance()->showCriticalMessage(tr("FlightGear/TerraSync Error"), tr("Please check if the path and command is correct.")); emit showCriticalMessageFromThread(tr("FlightGear Error"), tr("Please check if the path and command is correct."));
break; break;
} }
} }
...@@ -393,13 +237,13 @@ void QGCFlightGearLink::updateControls(quint64 time, float rollAilerons, float p ...@@ -393,13 +237,13 @@ void QGCFlightGearLink::updateControls(quint64 time, float rollAilerons, float p
QString state("%1\t%2\t%3\t%4\t%5\n"); QString state("%1\t%2\t%3\t%4\t%5\n");
state = state.arg(rollAilerons).arg(pitchElevator).arg(yawRudder).arg(true).arg(throttle); state = state.arg(rollAilerons).arg(pitchElevator).arg(yawRudder).arg(true).arg(throttle);
writeBytes(state.toAscii().constData(), state.length()); writeBytes(state.toAscii().constData(), state.length());
// qDebug() << "updated controls" << rollAilerons << pitchElevator << yawRudder << throttle; //qDebug() << "Updated controls" << rollAilerons << pitchElevator << yawRudder << throttle;
//qDebug() << "Updated controls" << state;
} }
else else
{ {
qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << isnan(rollAilerons) << ", pitch: " << isnan(pitchElevator) << ", yaw: " << isnan(yawRudder) << ", throttle: " << isnan(throttle); qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << isnan(rollAilerons) << ", pitch: " << isnan(pitchElevator) << ", yaw: " << isnan(yawRudder) << ", throttle: " << isnan(throttle);
} }
//qDebug() << "Updated controls" << state;
} }
void QGCFlightGearLink::writeBytes(const char* data, qint64 size) void QGCFlightGearLink::writeBytes(const char* data, qint64 size)
...@@ -425,7 +269,7 @@ void QGCFlightGearLink::writeBytes(const char* data, qint64 size) ...@@ -425,7 +269,7 @@ void QGCFlightGearLink::writeBytes(const char* data, qint64 size)
qDebug() << bytes; qDebug() << bytes;
qDebug() << "ASCII:" << ascii; qDebug() << "ASCII:" << ascii;
#endif #endif
if (connectState && socket) socket->writeDatagram(data, size, currentHost, currentPort); if (connectState && _udpCommSocket) _udpCommSocket->writeDatagram(data, size, currentHost, currentPort);
} }
/** /**
...@@ -441,15 +285,15 @@ void QGCFlightGearLink::readBytes() ...@@ -441,15 +285,15 @@ void QGCFlightGearLink::readBytes()
QHostAddress sender; QHostAddress sender;
quint16 senderPort; quint16 senderPort;
unsigned int s = socket->pendingDatagramSize(); unsigned int s = _udpCommSocket->pendingDatagramSize();
if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size" << std::endl; if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size" << std::endl;
socket->readDatagram(data, maxLength, &sender, &senderPort); _udpCommSocket->readDatagram(data, maxLength, &sender, &senderPort);
QByteArray b(data, s); QByteArray b(data, s);
// Print string // Print string
QString state(b); QString state(b);
// qDebug() << "FG LINK GOT:" << state; //qDebug() << "FG LINK GOT:" << state;
QStringList values = state.split("\t"); QStringList values = state.split("\t");
...@@ -610,7 +454,7 @@ void QGCFlightGearLink::readBytes() ...@@ -610,7 +454,7 @@ void QGCFlightGearLink::readBytes()
**/ **/
qint64 QGCFlightGearLink::bytesAvailable() qint64 QGCFlightGearLink::bytesAvailable()
{ {
return socket->pendingDatagramSize(); return _udpCommSocket->pendingDatagramSize();
} }
/** /**
...@@ -620,78 +464,464 @@ qint64 QGCFlightGearLink::bytesAvailable() ...@@ -620,78 +464,464 @@ qint64 QGCFlightGearLink::bytesAvailable()
**/ **/
bool QGCFlightGearLink::disconnectSimulation() bool QGCFlightGearLink::disconnectSimulation()
{ {
disconnect(process, SIGNAL(error(QProcess::ProcessError)), disconnect(_fgProcess, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(processError(QProcess::ProcessError))); this, SLOT(processError(QProcess::ProcessError)));
disconnect(mav, SIGNAL(hilControlsChanged(quint64, float, float, float, float, quint8, quint8)), this, SLOT(updateControls(quint64,float,float,float,float,quint8,quint8))); disconnect(mav, SIGNAL(hilControlsChanged(quint64, float, float, float, float, quint8, quint8)), this, SLOT(updateControls(quint64,float,float,float,float,quint8,quint8)));
disconnect(this, SIGNAL(hilStateChanged(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)), mav, SLOT(sendHilState(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float))); disconnect(this, SIGNAL(hilStateChanged(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)), mav, SLOT(sendHilState(quint64, float, float, float, float,float, float, double, double, double, float, float, float, float, float, float, float, float)));
disconnect(this, SIGNAL(sensorHilGpsChanged(quint64, double, double, double, int, float, float, float, float, float, float, float, int)), mav, SLOT(sendHilGps(quint64, double, double, double, int, float, float, float, float, float, float, float, int))); disconnect(this, SIGNAL(sensorHilGpsChanged(quint64, double, double, double, int, float, float, float, float, float, float, float, int)), mav, SLOT(sendHilGps(quint64, double, double, double, int, float, float, float, float, float, float, float, int)));
disconnect(this, SIGNAL(sensorHilRawImuChanged(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)), mav, SLOT(sendHilSensors(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32))); disconnect(this, SIGNAL(sensorHilRawImuChanged(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)), mav, SLOT(sendHilSensors(quint64,float,float,float,float,float,float,float,float,float,float,float,float,float,quint32)));
if (process) if (_fgProcess)
{
process->close();
delete process;
process = NULL;
}
if (terraSync)
{ {
terraSync->close(); _fgProcess->close();
delete terraSync; delete _fgProcess;
terraSync = NULL; _fgProcess = NULL;
} }
if (socket) if (_udpCommSocket)
{ {
socket->close(); _udpCommSocket->close();
delete socket; delete _udpCommSocket;
socket = NULL; _udpCommSocket = NULL;
} }
connectState = false; connectState = false;
emit simulationDisconnected(); emit simulationDisconnected();
emit simulationConnected(false); emit simulationConnected(false);
// Exit the thread
quit();
return !connectState; return !connectState;
} }
/** /// @brief Splits a space seperated set of command line arguments into a QStringList.
* @brief Connect the connection. /// Quoted strings are allowed and handled correctly.
* /// @param uiArgs Arguments to parse
* @return True if connection has been established, false if connection couldn't be established. /// @param argList Returned argument list
**/ /// @return Returns false if the argument list has mistmatced quotes within in.
bool QGCFlightGearLink::connectSimulation()
{
start(HighPriority); bool QGCFlightGearLink::parseUIArguments(QString uiArgs, QStringList& argList)
{
// FYI: The only reason this routine is public is so that we can reference it from a unit test.
// 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; i<uiArgs.size(); i++) {
const QChar chr = uiArgs[i];
if (chr == ' ') {
if (inQuotedString) {
// Space is inside quoted string leave it in
currentArg += chr;
continue;
} else {
if (previousSpace) {
// Disregard multiple spaces
continue;
} else {
// We have a space that is finishing an argument
previousSpace = true;
if (inQuotedString) {
MainWindow::instance()->showCriticalMessage(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; return true;
} }
void QGCFlightGearLink::printTerraSyncOutput() /// @brief Locates the specified argument in the argument list, returning the value for it.
{ /// @param uiArgList Argument list to search through
qDebug() << "TerraSync stdout:"; /// @param argLabel Argument label to search for
QByteArray byteArray = terraSync->readAllStandardOutput(); /// @param argValue Returned argument value if found
QStringList strLines = QString(byteArray).split("\n"); /// @return Returns true if argument found and argValue returned
foreach (QString line, strLines){ bool QGCFlightGearLink::_findUIArgument(const QStringList& uiArgList, const QString& argLabel, QString& argValue)
qDebug() << line; {
} QString regExpStr = argLabel + "=(.*)";
int index = uiArgList.indexOf(QRegExp(regExpStr, Qt::CaseInsensitive));
if (index != -1) {
QRegExp regExp(regExpStr);
index = regExp.indexIn(uiArgList[index]);
Q_ASSERT(index != -1);
argValue = regExp.cap(1);
return true;
} else {
return false;
}
} }
void QGCFlightGearLink::printTerraSyncError() /**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
bool QGCFlightGearLink::connectSimulation()
{ {
qDebug() << "TerraSync stderr:"; // We setup all the information we need to start FlightGear here such that if something goes
// wrong we can return false out of here. All of this happens on the main UI thread. Once we
// have that information setup we start the thread which will call run, which will in turn
// start the various FG processes on the seperate thread.
if (!mav) {
return false;
}
QString fgAppName;
QString fgRootPath; // FlightGear root data directory as specified by --fg-root
QStringList fgRootPathProposedList; // Directories we will attempt to search for --fg-root
bool fgRootDirOverride = false; // true: User has specified --fg-root from ui options
QString fgSceneryPath; // FlightGear scenery path as specified by --fg-scenery
bool fgSceneryDirOverride = false; // true: User has specified --fg-scenery from ui options
QDir fgAppDir; // Location of main FlightGear application
#if defined Q_OS_MACX
// Mac installs will default to the /Applications folder 99% of the time. Anything other than
// that is pretty non-standard so we don't try to get fancy beyond hardcoding that path.
fgAppDir.setPath("/Applications");
fgAppName = "FlightGear.app";
_fgProcessName = "./fgfs.sh";
_fgProcessWorkingDirPath = "/Applications/FlightGear.app/Contents/Resources/";
fgRootPathProposedList += "/Applications/FlightGear.app/Contents/Resources/data/";
#elif defined Q_OS_WIN32
_fgProcessName = "fgfs.exe";
// Windows installs are not as easy to determine. Default installation is to
// C:\Program Files\FlightGear, but that can be easily changed. That also doesn't
// tell us whether the user is running 32 or 64 bit which will both be installed there.
// The preferences for the fgrun app will tell us which version they are using
// and where it is. That is stored in the $APPDATA\fliggear.org\fgrun.prefs file. This
// looks to be a more stable location and way to determine app location so we use that.
fgAppName = "fgfs.exe";
const char* appdataEnv = "APPDATA";
if (!qgetenv(appdataEnv).isEmpty()) {
QString fgrunPrefsFile = QDir(qgetenv(appdataEnv).constData()).absoluteFilePath("flightgear.org/fgrun.prefs");
qDebug() << fgrunPrefsFile;
QFile file(fgrunPrefsFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString lookahead; // lookahead for continuation lines
while (!in.atEnd() || !lookahead.isEmpty()) {
QString line;
QRegExp regExp;
// Prefs file has strange format where a line prepended with "+" is a continuation of the previous line.
// So do a lookahead to determine if we have a continuation or not.
if (!lookahead.isEmpty()) {
line = lookahead;
lookahead.clear();
} else {
line = in.readLine();
}
if (!in.atEnd()) {
lookahead = in.readLine();
regExp.setPattern("^\\+(.*)");
if (regExp.indexIn(lookahead) == 0) {
Q_ASSERT(regExp.captureCount() == 1);
line += regExp.cap(1);
lookahead.clear();
}
}
regExp.setPattern("^fg_exe:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
QString fgExeLocationFullQualified = regExp.cap(1);
qDebug() << fgExeLocationFullQualified;
regExp.setPattern("(.*)\\\\fgfs.exe");
if (regExp.indexIn(fgExeLocationFullQualified) == 0 && regExp.captureCount() == 1) {
fgAppDir.setPath(regExp.cap(1));
_fgProcessWorkingDirPath = fgAppDir.absolutePath();
qDebug() << "fg_exe" << fgAppDir.absolutePath();
}
continue;
}
regExp.setPattern("^fg_root:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
fgRootPathProposedList += QDir(regExp.cap(1)).absolutePath();
qDebug() << "fg_root" << fgRootPathProposedList[0];
continue;
}
regExp.setPattern("^fg_scenery:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
// Scenery can contain multiple paths seperated by ';' so don't do QDir::absolutePath on it
fgSceneryPath = regExp.cap(1);
qDebug() << "fg_scenery" << fgSceneryPath;
continue;
}
}
}
}
#elif defined Q_OS_LINUX
// Linux installs to a location on the path so we don't need a directory to run the executable
fgAppName = "fgfs";
_fgProcessName = "fgfs";
fgRootPathProposedList += "/usr/share/flightgear/data/"; // Default Archlinux location
fgRootPathProposedList += "/usr/share/games/flightgear/"; // Default Ubuntu location
#else
#error Unknown OS build flavor
#endif
#ifndef Q_OS_LINUX
// Validate the FlightGear application directory location. Linux runs from path so we don't validate on that OS.
Q_ASSERT(!fgAppName.isEmpty());
QString fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
while (!QFileInfo(fgAppFullyQualified).exists()) {
QMessageBox msgBox(QMessageBox::Critical,
tr("FlightGear application not found"),
tr("FlightGear application not found at: %1").arg(fgAppFullyQualified),
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("I'll specify directory"), QMessageBox::ActionRole);
if (msgBox.exec() == QMessageBox::Cancel) {
return false;
}
// Let the user pick the right directory
QString dirPath = QFileDialog::getExistingDirectory(MainWindow::instance(), tr("Please select directory of FlightGear application : ") + fgAppName);
if (dirPath.isEmpty()) {
return false;
}
fgAppDir.setPath(dirPath);
fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
}
#endif
// Split the space seperated command line arguments coming in from the ui into a QStringList since
// that is what QProcess::start needs.
QStringList uiArgList;
bool mismatchedQuotes = parseUIArguments(startupArguments, uiArgList);
if (!mismatchedQuotes) {
MainWindow::instance()->showCriticalMessage(tr("FlightGear settings"), tr("Mismatched quotes in specified command line options"));
return false;
}
// Add the user specified arguments to our argument list
#ifdef DEBUG_FLIGHTGEAR_CONNECT
qDebug() << "\nSplit arguments" << uiArgList << "\n";
#endif
_fgArgList += uiArgList;
// If we have an --fg-root coming in from the ui options, that setting overrides any internal searching of
// proposed locations.
QString argValue;
fgRootDirOverride = _findUIArgument(_fgArgList, "--fg-root", argValue);
if (fgRootDirOverride) {
fgRootPathProposedList.clear();
fgRootPathProposedList += argValue;
qDebug() << "--fg-root override" << argValue;
}
QByteArray byteArray = terraSync->readAllStandardError(); // See if we can find an --fg-root directory from the proposed list.
QStringList strLines = QString(byteArray).split("\n"); Q_ASSERT(fgRootPath.isEmpty());
for (int i=0; i<fgRootPathProposedList.count(); i++) {
fgRootPath = fgRootPathProposedList[i];
if (QFileInfo(fgRootPath).isDir()) {
// We found it
break;
} else {
fgRootPath.clear();
}
}
// Alert the user if we couldn't find an --fg-root
if (fgRootPath.isEmpty()) {
QString errMsg;
if (fgRootDirOverride) {
errMsg = tr("--fg-root directory specified from ui option not found: %1").arg(fgRootPath);
} else if (fgRootPath.isEmpty()) {
errMsg = tr("Unable to automatically determine --fg-root directory location. You will need to specify --fg-root=<directory> as an additional command line parameter from ui.");
}
MainWindow::instance()->showCriticalMessage(tr("FlightGear settings"), errMsg);
return false;
}
foreach (QString line, strLines){ if (!fgRootDirOverride) {
qDebug() << line; _fgArgList += "--fg-root=" + fgRootPath;
} }
// Add --fg-scenery command line arg. If --fg-scenery is specified from the ui we use that instead.
// On Windows --fg-scenery is required on the command line otherwise FlightGear won't boot.
fgSceneryDirOverride = _findUIArgument(_fgArgList, "--fg-scenery", argValue);
if (fgSceneryDirOverride) {
fgSceneryPath = argValue;
qDebug() << "--fg-scenery override" << argValue;
} else if (!fgSceneryPath.isEmpty()) {
_fgArgList += "--fg-scenery=" + fgSceneryPath;
}
#ifdef Q_OS_WIN32
// Windows won't start without an --fg-scenery set. We don't validate the directory in the path since
// it can be multiple paths.
if (fgSceneryPath.isEmpty()) {
QString errMsg;
if (fgSceneryDirOverride) {
errMsg = tr("--fg-scenery directory specified from ui option not found: %1").arg(fgSceneryPath);
} else {
errMsg = tr("Unable to automatically determine --fg-scenery directory location. You will need to specify --fg-scenery=directory as an additional command line parameter from ui.");
}
MainWindow::instance()->showCriticalMessage(tr("FlightGear settings"), errMsg);
return false;
}
#else
Q_UNUSED(fgSceneryDirOverride);
#endif
// Setup and verify directory which contains QGC provided aircraft files
QString qgcAircraftDir(QApplication::applicationDirPath() + "/files/flightgear/Aircraft");
if (!QFileInfo(qgcAircraftDir).isDir()) {
MainWindow::instance()->showCriticalMessage(tr("Incorrect QGroundControl installation"), tr("Aircraft directory is missing: '%1'.").arg(qgcAircraftDir));
return false;
}
_fgArgList += "--fg-aircraft=" + qgcAircraftDir;
// Setup protocol we will be using to communicate with FlightGear
QString fgProtocol(mav->getSystemType() == MAV_TYPE_QUADROTOR ? "qgroundcontrol-quadrotor" : "qgroundcontrol-fixed-wing");
QString fgProtocolArg("--generic=socket,%1,300,127.0.0.1,%2,udp,%3");
_fgArgList << fgProtocolArg.arg("out").arg(port).arg(fgProtocol);
_fgArgList << fgProtocolArg.arg("in").arg(currentPort).arg(fgProtocol);
// Verify directory where FlightGear stores communicaton protocols.
QDir fgProtocolDir(fgRootPath);
if (!fgProtocolDir.cd("Protocol")) {
MainWindow::instance()->showCriticalMessage(tr("Incorrect FlightGear setup"), tr("Protocol directory is missing: '%1'. Command line parameter for --fg-root may be set incorrectly.").arg(fgProtocolDir.path()));
return false;
}
// Verify directory which contains QGC provided FlightGear communication protocol files
QDir qgcProtocolDir(QApplication::applicationDirPath() + "/files/flightgear/Protocol/");
if (!qgcProtocolDir.isReadable()) {
MainWindow::instance()->showCriticalMessage(tr("Incorrect QGroundControl installation"), tr("Protocol directory is missing (%1).").arg(qgcProtocolDir.path()));
return false;
}
// Communication protocol must be in FlightGear protocol directory. There does not appear to be any way
// around this by specifying something on the FlightGear command line. FG code does direct append
// of protocol xml file to $FG_ROOT and $FG_ROOT only allows a single directory to be specified.
QString fgProtocolXmlFile = fgProtocol + ".xml";
QString fgProtocolFileFullyQualified = fgProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (!QFileInfo(fgProtocolFileFullyQualified).exists()) {
QMessageBox msgBox(QMessageBox::Critical,
tr("FlightGear Failed to Start"),
tr("FlightGear Failed to Start. QGroundControl protocol (%1) not installed to FlightGear Protocol directory (%2)").arg(fgProtocolXmlFile).arg(fgProtocolDir.path()),
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("Fix it for me"), QMessageBox::ActionRole);
if (msgBox.exec() == QMessageBox::Cancel) {
return false;
}
// Make sure we can find the communication protocol file in QGC install before we attempt to copy to FlightGear
QString qgcProtocolFileFullyQualified = qgcProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (!QFileInfo(qgcProtocolFileFullyQualified).exists()) {
MainWindow::instance()->showCriticalMessage(tr("Incorrect QGroundControl installation"), tr("FlightGear protocol file missing: %1").arg(qgcProtocolFileFullyQualified));
return false;
}
// Now that we made it this far, we should be able to try to copy the protocol file to FlightGear.
bool succeeded = QFile::copy(qgcProtocolFileFullyQualified, fgProtocolFileFullyQualified);
if (!succeeded) {
#ifdef Q_OS_WIN32
QString copyCmd = QString("copy \"%1\" \"%2\"").arg(qgcProtocolFileFullyQualified).arg(fgProtocolFileFullyQualified);
copyCmd.replace("/", "\\");
#else
QString copyCmd = QString("sudo cp %1 %2").arg(qgcProtocolFileFullyQualified).arg(fgProtocolFileFullyQualified);
#endif
QMessageBox msgBox(QMessageBox::Critical,
tr("Copy failed"),
#ifdef Q_OS_WIN32
tr("Copy from (%1) to (%2) failed, possibly due to permissions issue. You will need to perform manually. Try pasting the following command into a Command Prompt which was started with Run as Administrator:\n\n").arg(qgcProtocolFileFullyQualified).arg(fgProtocolFileFullyQualified) + copyCmd,
#else
tr("Copy from (%1) to (%2) failed, possibly due to permissions issue. You will need to perform manually. Try pasting the following command into a shell:\n\n").arg(qgcProtocolFileFullyQualified).arg(fgProtocolFileFullyQualified) + copyCmd,
#endif
QMessageBox::Cancel,
MainWindow::instance());
msgBox.setWindowModality(Qt::ApplicationModal);
msgBox.addButton(tr("Copy to Clipboard"), QMessageBox::ActionRole);
if (msgBox.exec() != QMessageBox::Cancel) {
QApplication::clipboard()->setText(copyCmd);
}
return false;
}
}
// Start the engines to save a startup step
if (mav->getSystemType() == MAV_TYPE_QUADROTOR) {
// Start all engines of the quad
_fgArgList << "--prop:/engines/engine[0]/running=true";
_fgArgList << "--prop:/engines/engine[1]/running=true";
_fgArgList << "--prop:/engines/engine[2]/running=true";
_fgArgList << "--prop:/engines/engine[3]/running=true";
} else {
_fgArgList << "--prop:/engines/engine/running=true";
}
// We start out at our home position
_fgArgList << QString("--lat=%1").arg(UASManager::instance()->getHomeLatitude());
_fgArgList << QString("--lon=%1").arg(UASManager::instance()->getHomeLongitude());
// The altitude is not set because an altitude not equal to the ground altitude leads to a non-zero default throttle in flightgear
// Without the altitude-setting the aircraft is positioned on the ground
//_fgArgList << QString("--altitude=%1").arg(UASManager::instance()->getHomeAltitude());
#ifdef DEBUG_FLIGHTGEAR_CONNECT
// This tell FlightGear to output highest debug level of log output. Handy for debuggin failures by looking at the FG
// log files.
_fgArgList << "--log-level=debug";
#endif
start(HighPriority);
return true;
} }
void QGCFlightGearLink::printFgfsOutput() void QGCFlightGearLink::_printFgfsOutput(void)
{ {
qDebug() << "fgfs stdout:"; qDebug() << "fgfs stdout:";
QByteArray byteArray = process->readAllStandardOutput(); QByteArray byteArray = _fgProcess->readAllStandardOutput();
QStringList strLines = QString(byteArray).split("\n"); QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines){ foreach (QString line, strLines){
...@@ -699,11 +929,11 @@ void QGCFlightGearLink::printFgfsOutput() ...@@ -699,11 +929,11 @@ void QGCFlightGearLink::printFgfsOutput()
} }
} }
void QGCFlightGearLink::printFgfsError() void QGCFlightGearLink::_printFgfsError(void)
{ {
qDebug() << "fgfs stderr:"; qDebug() << "fgfs stderr:";
QByteArray byteArray = process->readAllStandardError(); QByteArray byteArray = _fgProcess->readAllStandardError();
QStringList strLines = QString(byteArray).split("\n"); QStringList strLines = QString(byteArray).split("\n");
foreach (QString line, strLines){ foreach (QString line, strLines){
......
...@@ -47,7 +47,6 @@ This file is part of the QGROUNDCONTROL project ...@@ -47,7 +47,6 @@ This file is part of the QGROUNDCONTROL project
class QGCFlightGearLink : public QGCHilLink class QGCFlightGearLink : public QGCHilLink
{ {
Q_OBJECT Q_OBJECT
//Q_INTERFACES(QGCFlightGearLinkInterface:LinkInterface)
public: public:
QGCFlightGearLink(UASInterface* mav, QString startupArguments, QString remoteHost=QString("127.0.0.1:49000"), QHostAddress host = QHostAddress::Any, quint16 port = 49005); QGCFlightGearLink(UASInterface* mav, QString startupArguments, QString remoteHost=QString("127.0.0.1:49000"), QHostAddress host = QHostAddress::Any, quint16 port = 49005);
...@@ -87,8 +86,13 @@ public: ...@@ -87,8 +86,13 @@ public:
void sensorHilEnabled(bool sensorHilEnabled) { void sensorHilEnabled(bool sensorHilEnabled) {
_sensorHilEnabled = sensorHilEnabled; _sensorHilEnabled = sensorHilEnabled;
} }
static bool parseUIArguments(QString uiArgs, QStringList& argList);
void run(); void run();
signals:
void showCriticalMessageFromThread(const QString& title, const QString& message);
public slots: public slots:
// void setAddress(QString address); // void setAddress(QString address);
...@@ -98,10 +102,6 @@ public slots: ...@@ -98,10 +102,6 @@ public slots:
/** @brief Send new control states to the simulation */ /** @brief Send new control states to the simulation */
void updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode); void updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode);
void updateActuators(quint64 time, float act1, float act2, float act3, float act4, float act5, float act6, float act7, float act8); void updateActuators(quint64 time, float act1, float act2, float act3, float act4, float act5, float act6, float act7, float act8);
// /** @brief Remove a host from broadcasting messages to */
// void removeHost(const QString& host);
// void readPendingDatagrams();
void processError(QProcess::ProcessError err);
/** @brief Set the simulator version as text string */ /** @brief Set the simulator version as text string */
void setVersion(const QString& version) void setVersion(const QString& version)
{ {
...@@ -130,12 +130,9 @@ public slots: ...@@ -130,12 +130,9 @@ public slots:
bool connectSimulation(); bool connectSimulation();
bool disconnectSimulation(); bool disconnectSimulation();
void printTerraSyncOutput();
void printTerraSyncError();
void printFgfsOutput();
void printFgfsError();
void setStartupArguments(QString startupArguments); void setStartupArguments(QString startupArguments);
void setBarometerOffset(float barometerOffsetkPa); void setBarometerOffset(float barometerOffsetkPa);
void processError(QProcess::ProcessError err);
protected: protected:
QString name; QString name;
...@@ -144,32 +141,29 @@ protected: ...@@ -144,32 +141,29 @@ protected:
quint16 currentPort; quint16 currentPort;
quint16 port; quint16 port;
int id; int id;
QUdpSocket* socket;
bool connectState; bool connectState;
quint64 bitsSentTotal;
quint64 bitsSentCurrent;
quint64 bitsSentMax;
quint64 bitsReceivedTotal;
quint64 bitsReceivedCurrent;
quint64 bitsReceivedMax;
quint64 connectionStartTime;
QMutex statisticsMutex;
QMutex dataMutex;
QTimer refreshTimer;
UASInterface* mav; UASInterface* mav;
QProcess* process;
QProcess* terraSync;
unsigned int flightGearVersion; unsigned int flightGearVersion;
QString startupArguments; QString startupArguments;
bool _sensorHilEnabled; bool _sensorHilEnabled;
float barometerOffsetkPa; float barometerOffsetkPa;
void setName(QString name); void setName(QString name);
signals: private slots:
void _printFgfsOutput(void);
void _printFgfsError(void);
private:
static bool _findUIArgument(const QStringList& uiArgList, const QString& argLabel, QString& argValue);
QString _fgProcessName; ///< FlightGear process to start
QString _fgProcessWorkingDirPath; ///< Working directory to start FG process in, empty for none
QStringList _fgArgList; ///< Arguments passed to FlightGear process
QUdpSocket* _udpCommSocket; ///< UDP communication sockect between FG and QGC
QProcess* _fgProcess; ///< FlightGear process
}; };
#endif // QGCFLIGHTGEARLINK_H #endif // QGCFLIGHTGEARLINK_H
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#include "FlightGearTest.h"
#include "QGCFlightGearLink.h"
/// @file
/// @brief FlightGearUnitTest HIL Simulation class
///
/// @author Don Gagne <don@thegagnes.com>
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; i<sizeof(rgTestCases)/sizeof(rgTestCases[0]); i++) {
const ParseUIArgumentsTestCase_t* testCase = &rgTestCases[i];
QStringList returnedArgList;
bool actualReturn = QGCFlightGearLink::parseUIArguments(testCase->args, 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);
}
}
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#ifndef TCPLINKTEST_H
#define TCPLINKTEST_H
#include <QObject>
#include <QtTest>
#include <QApplication>
#include "AutoTest.h"
#include "TCPLink.h"
#include "MultiSignalSpy.h"
/// @file
/// @brief FlightGear HIL Simulation unit tests
///
/// @author Don Gagne <don@thegagnes.com>
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
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>Flightgear</string> <string>FlightGear 3.0+</string>
</property> </property>
</item> </item>
<item> <item>
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QLabel" name="statusLabel"> <widget class="QLabel" name="statusLabel">
<property name="text"> <property name="text">
<string>No simulator active..</string> <string></string>
</property> </property>
</widget> </widget>
</item> </item>
......
#include "QGCHilFlightGearConfiguration.h" #include "QGCHilFlightGearConfiguration.h"
#include "ui_QGCHilFlightGearConfiguration.h"
#include "MainWindow.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 --control=mouse --disable-sound --disable-random-objects --disable-ai-traffic --shading-flat --fog-disable --disable-specular-highlight --disable-panel --disable-clouds --fdm=jsb --units-meters --enable-terrasync";
QGCHilFlightGearConfiguration::QGCHilFlightGearConfiguration(UAS* mav, QWidget *parent) :
QWidget(parent), QWidget(parent),
mav(mav), _mav(mav),
ui(new Ui::QGCHilFlightGearConfiguration) _mavSettingsSubGroup(NULL),
{ _resetOptionsAction(tr("Reset to default options"), this)
ui->setupUi(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 << "EasyStar";
items << "Rascal110-JSBSim"; items << "Rascal110-JSBSim";
items << "c172p"; items << "c172p";
items << "YardStik"; items << "YardStik";
items << "Malolo1"; items << "Malolo1";
_mavSettingsSubGroup = _mavSettingsSubGroupFixedWing;
} }
else if (mav->getSystemType() == MAV_TYPE_QUADROTOR) else if (_mav->getSystemType() == MAV_TYPE_QUADROTOR)
{ {
items << "arducopter"; items << "arducopter";
_mavSettingsSubGroup = _mavSettingsSubGroupQuadRotor;
} }
else else
{ {
// FIXME: Should disable all input, won't work. Show error message in the status label thing.
items << "<aircraft>"; items << "<aircraft>";
} }
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() 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() void QGCHilFlightGearConfiguration::on_startButton_clicked()
{ {
//XXX check validity of inputs //XXX check validity of inputs
QString options = ui->optionsPlainTextEdit->toPlainText(); QString options = _ui.optionsPlainTextEdit->toPlainText();
options.append(" --aircraft=" + ui->aircraftComboBox->currentText()); options.append(" --aircraft=" + _ui.aircraftComboBox->currentText());
mav->enableHilFlightGear(true, options, ui->sensorHilCheckBox->isChecked(), this); _mav->enableHilFlightGear(true, options, _ui.sensorHilCheckBox->isChecked(), this);
} }
void QGCHilFlightGearConfiguration::on_stopButton_clicked() void QGCHilFlightGearConfiguration::on_stopButton_clicked()
{ {
mav->stopHil(); _mav->stopHil();
} }
void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const QString& baroOffset) void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const QString& baroOffset)
...@@ -53,3 +129,16 @@ void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const ...@@ -53,3 +129,16 @@ void QGCHilFlightGearConfiguration::on_barometerOffsetLineEdit_textChanged(const
emit barometerOffsetChanged(baroOffset.toFloat()); 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);
}
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include "QGCFlightGearLink.h" #include "QGCFlightGearLink.h"
#include "UAS.h" #include "UAS.h"
#include "ui_QGCHilFlightGearConfiguration.h"
namespace Ui { namespace Ui {
class QGCHilFlightGearConfiguration; class QGCHilFlightGearConfiguration;
} }
...@@ -20,16 +22,33 @@ public: ...@@ -20,16 +22,33 @@ public:
~QGCHilFlightGearConfiguration(); ~QGCHilFlightGearConfiguration();
protected: protected:
UAS* mav;
private slots: private slots:
void on_startButton_clicked(); void on_startButton_clicked();
void on_stopButton_clicked(); void on_stopButton_clicked();
void on_barometerOffsetLineEdit_textChanged(const QString& baroOffset); void on_barometerOffsetLineEdit_textChanged(const QString& baroOffset);
void _setDefaultOptions(void);
void _showContextMenu(const QPoint& pt);
private: 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: signals:
void barometerOffsetChanged(float barometerOffsetkPa); void barometerOffsetChanged(float barometerOffsetkPa);
}; };
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="plainText"> <property name="plainText">
<string>--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 --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</string> <string></string>
</property> </property>
</widget> </widget>
</item> </item>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment