Newer
Older
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
/**
* @file
* @brief Definition of UDP connection (server) for unmanned vehicles
* @author Lorenz Meier <mavteam@student.ethz.ch>
* @author Thomas Gubler <thomasgubler@student.ethz.ch>
*
*/
#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
#include <iostream>
#include "QGCFlightGearLink.h"
#include "QGC.h"
#include "QGCQFileDialog.h"
#include "Vehicle.h"
#include "UAS.h"
#include "QGroundControlQmlGlobal.h"
// FlightGear _fgProcess start and connection is quite fragile. Uncomment the define below to get higher level of debug output
QGCFlightGearLink::QGCFlightGearLink(Vehicle* vehicle, QString startupArguments, QString remoteHost, QHostAddress host, quint16 port)
: _vehicle(vehicle)
, _udpCommSocket(NULL)
, _fgProcess(NULL)
, flightGearVersion(3)
, startupArguments(startupArguments)
, _sensorHilEnabled(true)
, barometerOffsetkPa(0.0f)
{
Lorenz Meier
committed
// 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/
moveToThread(this);
this->host = host;
this->port = port + _vehicle->id();
this->connectState = false;
this->currentPort = 49000 + _vehicle->id();
// 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, &QGCFlightGearLink::showCriticalMessageFromThread, qgcApp(), &QGCApplication::criticalMessageBoxOnMainThread);
Nate Weibley
committed
connect(this, &QGCFlightGearLink::disconnectSim, this, &QGCFlightGearLink::disconnectSimulation);
}
QGCFlightGearLink::~QGCFlightGearLink()
{ //do not disconnect unless it is connected.
//disconnectSimulation will delete the memory that was allocated for process, terraSync and _udpCommSocket
if(connectState){
disconnectSimulation();
}
}
/// @brief Runs the simulation thread. We do setup work here which needs to happen in the separate thread.
void QGCFlightGearLink::run()
{
// We communicate with FlightGear over a UDP _udpCommSocket
_udpCommSocket = new QUdpSocket(this);
Q_CHECK_PTR(_udpCommSocket);
_udpCommSocket->moveToThread(this);
_udpCommSocket->bind(host, port, QAbstractSocket::ReuseAddressHint);
QObject::connect(_udpCommSocket, &QUdpSocket::readyRead, this, &QGCFlightGearLink::readBytes);
// Connect to the various HIL signals that we use to then send information across the UDP protocol to FlightGear.
connect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCFlightGearLink::updateControls);
connect(this, &QGCFlightGearLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState);
connect(this, &QGCFlightGearLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps);
connect(this, &QGCFlightGearLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors);
connect(this, &QGCFlightGearLink::sensorHilOpticalFlowChanged, _vehicle->uas(), &UAS::sendHilOpticalFlow);
// Start a new QProcess to run FlightGear in
_fgProcess = new QProcess(this);
Q_CHECK_PTR(_fgProcess);
_fgProcess->moveToThread(this);
connect(_fgProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, &QGCFlightGearLink::processError);
//#ifdef DEBUG_FLIGHTGEAR_CONNECT
connect(_fgProcess, &QProcess::readyReadStandardOutput, this, &QGCFlightGearLink::_printFgfsOutput);
connect(_fgProcess, &QProcess::readyReadStandardError, this, &QGCFlightGearLink::_printFgfsError);
//#endif
qDebug() << "Working directory" << _fgProcess->workingDirectory();
// On Windows we need to full qualify the location of the excecutable. The call to setWorkingDirectory only
// sets the QProcess context, not the QProcess::start context. For some strange reason this is not the case on
// OSX.
QDir fgProcessFullyQualified(_fgProcessWorkingDirPath);
_fgProcessName = fgProcessFullyQualified.absoluteFilePath(_fgProcessName);
qDebug() << "\nStarting FlightGear" << _fgProcessWorkingDirPath << _fgProcessName << _fgArgList << "\n";
emit simulationConnected(connectState);
exec();
}
void QGCFlightGearLink::setPort(int port)
{
this->port = port;
}
void QGCFlightGearLink::processError(QProcess::ProcessError err)
{
switch(err)
{
case QProcess::FailedToStart:
emit showCriticalMessageFromThread(tr("FlightGear Failed to Start"), _fgProcess->errorString());
emit showCriticalMessageFromThread(tr("FlightGear Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear"));
emit showCriticalMessageFromThread(tr("FlightGear Start Timed Out"), 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"));
emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), 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."));
/**
* @param host Hostname in standard formatting, e.g. localhost:14551 or 192.168.1.1:14551
*/
void QGCFlightGearLink::setRemoteHost(const QString& host)
{
if (host.contains(":"))
{
//qDebug() << "HOST: " << host.split(":").first();
QHostInfo info = QHostInfo::fromName(host.split(":").first());
if (info.error() == QHostInfo::NoError)
{
// Add host
QList<QHostAddress> hostAddresses = info.addresses();
QHostAddress address;
for (int i = 0; i < hostAddresses.size(); i++)
{
// Exclude loopback IPv4 and all IPv6 addresses
if (!hostAddresses.at(i).toString().contains(":"))
{
address = hostAddresses.at(i);
}
}
currentHost = address;
//qDebug() << "Address:" << address.toString();
// Set port according to user input
}
}
else
{
QHostInfo info = QHostInfo::fromName(host);
if (info.error() == QHostInfo::NoError)
{
// Add host
currentHost = info.addresses().first();
}
}
}
void QGCFlightGearLink::updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode)
Q_UNUSED(time);
Q_UNUSED(systemMode);
Q_UNUSED(navMode);
if(!qIsNaN(rollAilerons) && !qIsNaN(pitchElevator) && !qIsNaN(yawRudder) && !qIsNaN(throttle))
Thomas Gubler
committed
{
QString state("%1\t%2\t%3\t%4\t%5\n");
state = state.arg(rollAilerons).arg(pitchElevator).arg(yawRudder).arg(true).arg(throttle);
emit _invokeWriteBytes(state.toLatin1());
//qDebug() << "Updated controls" << rollAilerons << pitchElevator << yawRudder << throttle;
//qDebug() << "Updated controls" << state;
Thomas Gubler
committed
}
else
{
qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << qIsNaN(rollAilerons) << ", pitch: " << qIsNaN(pitchElevator) << ", yaw: " << qIsNaN(yawRudder) << ", throttle: " << qIsNaN(throttle);
Thomas Gubler
committed
}
void QGCFlightGearLink::_writeBytes(const QByteArray data)
{
#ifdef QGCFlightGearLink_DEBUG
for (int i=0, size = data.size(); i<size; i++)
{
unsigned char v = data[i];
bytes.append(QString().sprintf("%02x ", v));
if (data[i] > 31 && data[i] < 127)
{
}
else
{
ascii.append(219);
}
}
qDebug() << "Sent" << size << "bytes to" << currentHost.toString() << ":" << currentPort << "data:";
qDebug() << bytes;
qDebug() << "ASCII:" << ascii;
#endif
if (connectState && _udpCommSocket) _udpCommSocket->writeDatagram(data, currentHost, currentPort);
}
/**
* @brief Read a number of bytes from the interface.
*
* @param data Pointer to the data byte array to write the bytes to
* @param maxLength The maximum number of bytes to write
**/
void QGCFlightGearLink::readBytes()
{
const qint64 maxLength = 65536;
char data[maxLength];
QHostAddress sender;
quint16 senderPort;
if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size" << std::endl;
QByteArray b(data, s);
QStringList values = state.split("\t");
// Check length
Thomas Gubler
committed
if (values.size() != nValues)
Thomas Gubler
committed
qDebug() << "RETURN LENGTH MISMATCHING EXPECTED" << nValues << "BUT GOT" << values.size();
emit showCriticalMessageFromThread(tr("FlightGear HIL"),
tr("Flight Gear protocol file '%1' is out of date. Quit %2. Delete the file and restart %2 to fix.").arg(_fgProtocolFileFullyQualified).arg(qgcApp()->applicationName()));
// Parse string
float roll, pitch, yaw, rollspeed, pitchspeed, yawspeed;
float ind_airspeed;
float true_airspeed;
float vx, vy, vz, xacc, yacc, zacc;
float diff_pressure;
float temperature;
float abs_pressure;
float mag_variation, mag_dip, xmag_ned, ymag_ned, zmag_ned, xmag_body, ymag_body, zmag_body;
lat = values.at(1).toDouble();
lon = values.at(2).toDouble();
alt = values.at(3).toDouble();
roll = values.at(4).toFloat();
pitch = values.at(5).toFloat();
yaw = values.at(6).toFloat();
rollspeed = values.at(7).toFloat();
pitchspeed = values.at(8).toFloat();
yawspeed = values.at(9).toFloat();
xacc = values.at(10).toFloat();
yacc = values.at(11).toFloat();
zacc = values.at(12).toFloat();
vx = values.at(13).toFloat();
vy = values.at(14).toFloat();
vz = values.at(15).toFloat();
true_airspeed = values.at(16).toFloat();
Thomas Gubler
committed
mag_variation = values.at(17).toFloat();
mag_dip = values.at(18).toFloat();
Thomas Gubler
committed
temperature = values.at(19).toFloat();
abs_pressure = values.at(20).toFloat() * 1e2f; //convert to Pa from hPa
abs_pressure += barometerOffsetkPa * 1e3f; //add offset, convert from kPa to Pa
alt_agl = values.at(21).toFloat();
//calculate differential pressure
const float air_gas_constant = 287.1f; // J/(kg * K)
const float absolute_null_celsius = -273.15f; // °C
float density = abs_pressure / (air_gas_constant * (temperature - absolute_null_celsius));
diff_pressure = true_airspeed * true_airspeed * density / 2.0f;
//qDebug() << "diff_pressure: " << diff_pressure << "abs_pressure: " << abs_pressure;
/* Calculate indicated airspeed */
const float air_density_sea_level_15C = 1.225f; //kg/m^3
if (diff_pressure > 0)
{
ind_airspeed = sqrtf((2.0f*diff_pressure) / air_density_sea_level_15C);
} else
{
ind_airspeed = -sqrtf((2.0f*fabsf(diff_pressure)) / air_density_sea_level_15C);
}
//qDebug() << "ind_airspeed: " << ind_airspeed << "true_airspeed: " << true_airspeed;
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
//qDebug() << "sensorHilEnabled: " << sensorHilEnabled;
if (_sensorHilEnabled)
{
quint16 fields_changed = 0xFFF; //set all 12 used bits
float pressure_alt = alt;
xmag_ned = cosf(mag_variation);
ymag_ned = sinf(mag_variation);
zmag_ned = sinf(mag_dip);
float tempMagLength = sqrtf(xmag_ned*xmag_ned + ymag_ned*ymag_ned + zmag_ned*zmag_ned);
xmag_ned = xmag_ned / tempMagLength;
ymag_ned = ymag_ned / tempMagLength;
zmag_ned = zmag_ned / tempMagLength;
//transform magnetic measurement to body frame coordinates
double cosPhi = cos(roll);
double sinPhi = sin(roll);
double cosThe = cos(pitch);
double sinThe = sin(pitch);
double cosPsi = cos(yaw);
double sinPsi = sin(yaw);
float R_B_N[3][3];
R_B_N[0][0] = cosThe * cosPsi;
R_B_N[0][1] = -cosPhi * sinPsi + sinPhi * sinThe * cosPsi;
R_B_N[0][2] = sinPhi * sinPsi + cosPhi * sinThe * cosPsi;
R_B_N[1][0] = cosThe * sinPsi;
R_B_N[1][1] = cosPhi * cosPsi + sinPhi * sinThe * sinPsi;
R_B_N[1][2] = -sinPhi * cosPsi + cosPhi * sinThe * sinPsi;
R_B_N[2][0] = -sinThe;
R_B_N[2][1] = sinPhi * cosThe;
R_B_N[2][2] = cosPhi * cosThe;
Eigen::Matrix3f R_B_N_M = Eigen::Map<Eigen::Matrix3f>((float*)R_B_N).eval();
Eigen::Vector3f mag_ned(xmag_ned, ymag_ned, zmag_ned);
Eigen::Vector3f mag_body = R_B_N_M * mag_ned;
xmag_body = mag_body(0);
ymag_body = mag_body(1);
zmag_body = mag_body(2);
emit sensorHilRawImuChanged(QGC::groundTimeUsecs(), xacc, yacc, zacc, rollspeed, pitchspeed, yawspeed,
xmag_body, ymag_body, zmag_body, abs_pressure*1e-2f, diff_pressure*1e-2f, pressure_alt, temperature, fields_changed); //Pressure in hPa for _vehicle->uas()link
// qDebug() << "sensorHilRawImuChanged " << xacc << yacc << zacc << rollspeed << pitchspeed << yawspeed << xmag << ymag << zmag << abs_pressure << diff_pressure << pressure_alt << temperature;
int gps_fix_type = 3;
float vel = sqrt(vx*vx + vy*vy + vz*vz);
float cog = yaw;
int satellites = 8;
emit sensorHilGpsChanged(QGC::groundTimeUsecs(), lat, lon, alt, gps_fix_type, eph, epv, vel, vx, vy, vz, cog, satellites);
// qDebug() << "sensorHilGpsChanged " << lat << lon << alt << vel;
// Send Optical Flow message. For now we set the flow quality to 0 and just write the ground_distance field
float distanceMeasurement = -1.0; // -1 means invalid value
if (fabsf(roll) < 0.87 && fabsf(pitch) < 0.87) // return a valid value only for decent angles
{
distanceMeasurement = fabsf((float)(1.0/cosPhi * 1.0/cosThe * alt_agl)); // assuming planar ground
}
emit sensorHilOpticalFlowChanged(QGC::groundTimeUsecs(), 0, 0, 0.0f,
0.0f, 0.0f, distanceMeasurement);
} else {
emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
vx, vy, vz,
ind_airspeed, true_airspeed,
xacc, yacc, zacc);
//qDebug() << "hilStateChanged " << (qint32)lat << (qint32)lon << (qint32)alt;
// // Echo data for debugging purposes
// std::cerr << __FILE__ << __LINE__ << "Received datagram:" << std::endl;
// int i;
// for (i=0; i<s; i++)
// {
// unsigned int v=data[i];
// fprintf(stderr,"%02x ", v);
// }
// std::cerr << std::endl;
}
/**
* @brief Get the number of bytes to read.
*
* @return The number of bytes to read
**/
qint64 QGCFlightGearLink::bytesAvailable()
{
}
/**
* @brief Disconnect the connection.
*
* @return True if connection has been disconnected, false if connection couldn't be disconnected.
**/
{
disconnect(_fgProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, &QGCFlightGearLink::processError);
disconnect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCFlightGearLink::updateControls);
disconnect(this, &QGCFlightGearLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState);
disconnect(this, &QGCFlightGearLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps);
disconnect(this, &QGCFlightGearLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors);
disconnect(this, &QGCFlightGearLink::sensorHilOpticalFlowChanged, _vehicle->uas(), &UAS::sendHilOpticalFlow);
Nate Weibley
committed
_fgProcess->deleteLater();
Nate Weibley
committed
_udpCommSocket->deleteLater();
connectState = false;
Lorenz Meier
committed
emit simulationDisconnected();
emit simulationConnected(false);
return !connectState;
}
/// @brief Splits a space separated set of command line arguments into a QStringList.
/// @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)
{
// 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) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("FlightGear failed to start. There are 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.
inQuotedString = !inQuotedString;
previousSpace = false;
} else {
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();
}
}
/// @brief Locates the specified argument in the argument list, returning the value for it.
/// @param uiArgList Argument list to search through
/// @param argLabel Argument label to search for
/// @param argValue Returned argument value if found
/// @return Returns true if argument found and argValue returned
bool QGCFlightGearLink::_findUIArgument(const QStringList& uiArgList, const QString& argLabel, QString& argValue)
{
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;
}
/**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
// 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 separate thread.
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
// Reset the list of arguments which will be provided to FG to the arguments set by the user via the UI
// First split the space separated 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) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("FlightGear failed to start. There are mismatched quotes in specified command line options"));
return false;
}
#ifdef DEBUG_FLIGHTGEAR_CONNECT
qDebug() << "\nSplit arguments" << uiArgList << "\n";
#endif
// Now set the FG arguments to the arguments from the UI
_fgArgList = uiArgList;
// 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";
_fgProcessWorkingDirPath = "/Applications/FlightGear.app/Contents/MacOS/";
if(!QFileInfo(_fgProcessWorkingDirPath + _fgProcessName).exists()){
// old path
_fgProcessName = "./fgfs.sh";
_fgProcessWorkingDirPath = "/Applications/FlightGear.app/Contents/Resources/";
}
fgRootPathProposedList += "/Applications/FlightGear.app/Contents/Resources/data/";
// 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);
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));
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];
regExp.setPattern("^fg_scenery:(.*)");
if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
// Scenery can contain multiple paths separated 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";
fgRootPathProposedList += "/usr/share/flightgear/data/"; // Default Archlinux location
fgRootPathProposedList += "/usr/share/games/flightgear/"; // Default Ubuntu location
// 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;
}
QString dirPath = QGCQFileDialog::getExistingDirectory(MainWindow::instance(), tr("Please select directory of FlightGear application : ") + fgAppName);
if (dirPath.isEmpty()) {
return false;
}
fgAppDir.setPath(dirPath);
fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
}
#endif
// 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;
}
// See if we can find an --fg-root directory from the proposed list.
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.");
}
QGCMessageBox::critical(tr("FlightGear HIL"), errMsg);
if (!fgRootDirOverride) {
_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;
_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
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.");
}
QGCMessageBox::critical(tr("FlightGear HIL"), errMsg);
// Setup and verify directory which contains QGC provided aircraft files
QString qgcAircraftDir(QApplication::applicationDirPath() + "/flightgear/Aircraft");
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect %1 installation. Aircraft directory is missing: '%2'.").arg(qgcApp()->applicationName()).arg(qgcAircraftDir));
return false;
}
_fgArgList += "--fg-aircraft=" + qgcAircraftDir;
QString fgProtocol(_vehicle->vehicleType() == 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")) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect FlightGear setup. Protocol directory is missing: '%1'. Command line parameter for --fg-root may be set incorrectly.").arg(fgProtocolDir.path()));
// Verify directory which contains QGC provided FlightGear communication protocol files
QDir qgcProtocolDir(QApplication::applicationDirPath() + "/flightgear/Protocol/");
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect installation. Protocol directory is missing (%1).").arg(qgcProtocolDir.path()));
// Make sure we can find the communication protocol file in QGC install
QString fgProtocolXmlFile = fgProtocol + ".xml";
QString qgcProtocolFileFullyQualified = qgcProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (!QFileInfo(qgcProtocolFileFullyQualified).exists()) {
QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect installation. FlightGear protocol file missing: %1").arg(qgcProtocolFileFullyQualified));
// 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.
_fgProtocolFileFullyQualified = fgProtocolDir.absoluteFilePath(fgProtocolXmlFile);
if (QFileInfo(_fgProtocolFileFullyQualified).exists()) {
// Verify that the file is current by comparing it against the one in QGC
QFile fgFile(_fgProtocolFileFullyQualified);
QFile qgcFile(qgcProtocolFileFullyQualified);
if (!fgFile.open(QIODevice::ReadOnly) ||
!qgcFile.open(QIODevice::ReadOnly)) {
QGCMessageBox::warning(tr("FlightGear HIL"), tr("Unable to verify that protocol file %1 is current. "
"If file is out of date, you may experience problems. "
"Safest approach is to delete the file manually and allow %2 install the latest file.").arg(qgcApp()->applicationName()).arg(_fgProtocolFileFullyQualified));
QByteArray fgBytes = fgFile.readAll();
QByteArray qgcBytes = qgcFile.readAll();
fgFile.close();
qgcFile.close();
QGCMessageBox::warning(tr("FlightGear HIL"), tr("FlightGear protocol file %1 is out of date. It will be deleted, which will cause %2 to install the latest version of the file.").arg(_fgProtocolFileFullyQualified).arg(qgcApp()->applicationName()));
if (!QFile::remove(_fgProtocolFileFullyQualified)) {
QGCMessageBox::warning(tr("FlightGear HIL"), tr("Delete of protocol file failed. You will have to manually delete the file."));
return false;
}
}
}
if (!QFileInfo(_fgProtocolFileFullyQualified).exists()) {
QMessageBox msgBox(QMessageBox::Critical,
tr("FlightGear Failed to Start"),
tr("FlightGear Failed to Start. %1 protocol (%2) not installed to FlightGear Protocol directory (%3)").arg(qgcApp()->applicationName()).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;
}
// 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);
QString copyCmd = QString("copy \"%1\" \"%2\"").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
QString copyCmd = QString("sudo cp %1 %2").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
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,
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;
}
}
if (_vehicle->vehicleType() == 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";
}
QGeoCoordinate homePosition = QGroundControlQmlGlobal::flightMapPosition();
_fgArgList << QString("--lat=%1").arg(homePosition.latitude());
_fgArgList << QString("--lon=%1").arg(homePosition.longitude());
// 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(homePosition.altitude());
#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;
QStringList strLines = QString(byteArray).split("\n");
foreach (const QString &line, strLines){
QStringList strLines = QString(byteArray).split("\n");
foreach (const QString &line, strLines){
/**
* @brief Set the startup arguments used to start flightgear
*
**/
void QGCFlightGearLink::setStartupArguments(QString startupArguments)
{
this->startupArguments = startupArguments;
}
/**
* @brief Check if connection is active.
*
* @return True if link is connected, false otherwise.
**/
bool QGCFlightGearLink::isConnected()
{
return connectState;
}
QString QGCFlightGearLink::getName()
{
return name;
}