Skip to content
QGCFlightGearLink.cc 40.7 KiB
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
lm's avatar
lm committed
 *   @see Flightgear Manual http://mapserver.flightgear.org/getstart.pdf
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
Thomas Gubler's avatar
Thomas Gubler committed
 *   @author Thomas Gubler <thomasgubler@student.ethz.ch>
 *
 */

#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
Don Gagne's avatar
Don Gagne committed
#include <QHostInfo>
Don Gagne's avatar
Don Gagne committed
#include <QMessageBox>
#include <QClipboard>
#include <Eigen/Eigen>
#include "QGCFlightGearLink.h"
#include "QGC.h"
Don Gagne's avatar
Don Gagne committed
#include "QGCFileDialog.h"
#include "QGCMessageBox.h"
#include "HomePositionManager.h"
#include "QGCApplication.h"
#include "Vehicle.h"
#include "UAS.h"
Don Gagne's avatar
Don Gagne committed
// FlightGear _fgProcess start and connection is quite fragile. Uncomment the define below to get higher level of debug output
Don Gagne's avatar
Don Gagne committed
// for tracking down problems.
Don Gagne's avatar
Don Gagne committed
//#define DEBUG_FLIGHTGEAR_CONNECT
Don Gagne's avatar
Don Gagne committed

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)
    // 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->port = port + _vehicle->id();
    this->currentPort = 49000 + _vehicle->id();
Don Gagne's avatar
Don Gagne committed
    this->name = tr("FlightGear 3.0+ Link (port:%1)").arg(port);
LM's avatar
LM committed
    setRemoteHost(remoteHost);
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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);
    connect(this, &QGCFlightGearLink::disconnectSim, this, &QGCFlightGearLink::disconnectSimulation);
{   //do not disconnect unless it is connected.
Don Gagne's avatar
Don Gagne committed
    //disconnectSimulation will delete the memory that was allocated for proces, terraSync and _udpCommSocket
    if(connectState){
       disconnectSimulation();
    }
Don Gagne's avatar
Don Gagne committed
/// @brief Runs the simulation thread. We do setup work here which needs to happen in the seperate thread.
    Q_ASSERT(_vehicle);
Don Gagne's avatar
Don Gagne committed
    Q_ASSERT(!_fgProcessName.isEmpty());
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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);
Don Gagne's avatar
Don Gagne committed
    // 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);
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // Start a new QProcess to run FlightGear in
    _fgProcess = new QProcess(this);
    Q_CHECK_PTR(_fgProcess);
    _fgProcess->moveToThread(this);
dogmaphobic's avatar
dogmaphobic committed

    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
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    if (!_fgProcessWorkingDirPath.isEmpty()) {
Don Gagne's avatar
Don Gagne committed
        _fgProcess->setWorkingDirectory(_fgProcessWorkingDirPath);
dogmaphobic's avatar
dogmaphobic committed
        qDebug() << "Working directory" << _fgProcess->workingDirectory();
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
#ifdef Q_OS_WIN32
dogmaphobic's avatar
dogmaphobic committed
    // 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);
dogmaphobic's avatar
dogmaphobic committed
    _fgProcessName = fgProcessFullyQualified.absoluteFilePath(_fgProcessName);
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
#ifdef DEBUG_FLIGHTGEAR_CONNECT
Don Gagne's avatar
Don Gagne committed
    qDebug() << "\nStarting FlightGear" << _fgProcessWorkingDirPath << _fgProcessName << _fgArgList << "\n";
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    _fgProcess->start(_fgProcessName, _fgArgList);
Don Gagne's avatar
Don Gagne committed
    connectState = true;
dogmaphobic's avatar
dogmaphobic committed

    emit simulationConnected(connectState);
Don Gagne's avatar
Don Gagne committed
    emit simulationConnected();
    exec();
}

void QGCFlightGearLink::setPort(int port)
{
    this->port = port;
lm's avatar
lm committed
    disconnectSimulation();
    connectSimulation();
lm's avatar
lm committed
void QGCFlightGearLink::processError(QProcess::ProcessError err)
{
    switch(err)
    {
    case QProcess::FailedToStart:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("FlightGear Failed to Start"), _fgProcess->errorString());
lm's avatar
lm committed
        break;
    case QProcess::Crashed:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("FlightGear Crashed"), tr("This is a FlightGear-related problem. Please upgrade FlightGear"));
lm's avatar
lm committed
        break;
    case QProcess::Timedout:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("FlightGear Start Timed Out"), tr("Please check if the path and command is correct"));
lm's avatar
lm committed
        break;
    case QProcess::WriteError:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
lm's avatar
lm committed
        break;
    case QProcess::ReadError:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("Could not Communicate with FlightGear"), tr("Please check if the path and command is correct"));
lm's avatar
lm committed
        break;
    case QProcess::UnknownError:
    default:
Don Gagne's avatar
Don Gagne committed
        emit showCriticalMessageFromThread(tr("FlightGear Error"), tr("Please check if the path and command is correct."));
lm's avatar
lm committed
        break;
    }
}

/**
 * @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
lm's avatar
lm committed
            currentPort = host.split(":").last().toInt();
        }
    }
    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)
lm's avatar
lm committed
{
LM's avatar
LM committed
    // magnetos,aileron,elevator,rudder,throttle\n

lm's avatar
lm committed
    //float magnetos = 3.0f;
lm's avatar
lm committed
    Q_UNUSED(time);
    Q_UNUSED(systemMode);
    Q_UNUSED(navMode);
LM's avatar
LM committed

    if(!qIsNaN(rollAilerons) && !qIsNaN(pitchElevator) && !qIsNaN(yawRudder) && !qIsNaN(throttle))
    {
        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());
Don Gagne's avatar
Don Gagne committed
        //qDebug() << "Updated controls" << rollAilerons << pitchElevator << yawRudder << throttle;
        //qDebug() << "Updated controls" << state;
        qDebug() << "HIL: Got NaN values from the hardware: isnan output: roll: " << qIsNaN(rollAilerons) << ", pitch: " << qIsNaN(pitchElevator) << ", yaw: " << qIsNaN(yawRudder) << ", throttle: " << qIsNaN(throttle);
void QGCFlightGearLink::_writeBytes(const QByteArray data)
lm's avatar
lm committed
    //#define QGCFlightGearLink_DEBUG
lm's avatar
lm committed
    QString bytes;
    QString ascii;
    for (int i=0, size = data.size(); i<size; i++)
lm's avatar
lm committed
    {
        unsigned char v = data[i];
        bytes.append(QString().sprintf("%02x ", v));
        if (data[i] > 31 && data[i] < 127)
lm's avatar
lm committed
            ascii.append(data[i]);
lm's avatar
lm committed
        else
        {
            ascii.append(219);
        }
    }
    qDebug() << "Sent" << size << "bytes to" << currentHost.toString() << ":" << currentPort << "data:";
    qDebug() << bytes;
    qDebug() << "ASCII:" << ascii;
    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;
Don Gagne's avatar
Don Gagne committed
    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;
Don Gagne's avatar
Don Gagne committed
    _udpCommSocket->readDatagram(data, maxLength, &sender, &senderPort);
lm's avatar
lm committed

    // Print string
    QString state(b);
Don Gagne's avatar
Don Gagne committed
    //qDebug() << "FG LINK GOT:" << state;
lm's avatar
lm committed

lm's avatar
lm committed
    QStringList values = state.split("\t");

    // Check length
    const int nValues = 22;
lm's avatar
lm committed
    {
        qDebug() << "RETURN LENGTH MISMATCHING EXPECTED" << nValues << "BUT GOT" << values.size();
lm's avatar
lm committed
        qDebug() << state;
        emit showCriticalMessageFromThread(tr("FlightGear HIL"),
                                           tr("Flight Gear protocol file '%1' is out of date. Quit QGroundControl. Delete the file and restart QGroundControl to fix.").arg(_fgProtocolFileFullyQualified));
        disconnectSimulation();
lm's avatar
lm committed
        return;
    }
lm's avatar
lm committed

    // Parse string
    float roll, pitch, yaw, rollspeed, pitchspeed, yawspeed;
    double lat, lon, alt;
Thomas Gubler's avatar
Thomas Gubler committed
    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;
    float alt_agl;
lm's avatar
lm committed

Lorenz Meier's avatar
Lorenz Meier committed
    lat = values.at(1).toDouble();
    lon = values.at(2).toDouble();
    alt = values.at(3).toDouble();
Thomas Gubler's avatar
Thomas Gubler committed
    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();
lm's avatar
lm committed

Thomas Gubler's avatar
Thomas Gubler committed
    xacc = values.at(10).toFloat();
    yacc = values.at(11).toFloat();
    zacc = values.at(12).toFloat();
lm's avatar
lm committed

Thomas Gubler's avatar
Thomas Gubler committed
    vx = values.at(13).toFloat();
    vy = values.at(14).toFloat();
    vz = values.at(15).toFloat();

    true_airspeed = values.at(16).toFloat();

    mag_variation = values.at(17).toFloat();
    mag_dip = values.at(18).toFloat();
    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
lm's avatar
lm committed

    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;
lm's avatar
lm committed
    // Send updated state
Thomas Gubler's avatar
Thomas Gubler committed
    //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;

dogmaphobic's avatar
dogmaphobic committed
        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;
dogmaphobic's avatar
dogmaphobic committed
        R_B_N[2][0] = -sinThe;
        R_B_N[2][1] = sinPhi * cosThe;
        R_B_N[2][2] = cosPhi * cosThe;
Thomas Gubler's avatar
Thomas Gubler committed

        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
Thomas Gubler's avatar
Thomas Gubler committed

//        qDebug()  << "sensorHilRawImuChanged " << xacc  << yacc << zacc  << rollspeed << pitchspeed << yawspeed << xmag << ymag << zmag << abs_pressure << diff_pressure << pressure_alt << temperature;
        int gps_fix_type = 3;
Don Gagne's avatar
Don Gagne committed
        float eph = 0.3f;
        float epv = 0.6f;
Thomas Gubler's avatar
Thomas Gubler committed
        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);
Thomas Gubler's avatar
Thomas Gubler committed
    } else {
        emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
lm's avatar
lm committed
                         pitchspeed, yawspeed, lat, lon, alt,
Thomas Gubler's avatar
Thomas Gubler committed
                         vx, vy, vz,
                         ind_airspeed, true_airspeed,
                         xacc, yacc, zacc);
        //qDebug()  << "hilStateChanged " << (qint32)lat << (qint32)lon << (qint32)alt;
Thomas Gubler's avatar
Thomas Gubler committed
    }
lm's avatar
lm committed

    //    // 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()
{
Don Gagne's avatar
Don Gagne committed
    return _udpCommSocket->pendingDatagramSize();
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
lm's avatar
lm committed
bool QGCFlightGearLink::disconnectSimulation()
    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);
LM's avatar
LM committed

Don Gagne's avatar
Don Gagne committed
    if (_fgProcess)
lm's avatar
lm committed
    {
Don Gagne's avatar
Don Gagne committed
        _fgProcess->close();
Don Gagne's avatar
Don Gagne committed
        _fgProcess = NULL;
lm's avatar
lm committed
    }
Don Gagne's avatar
Don Gagne committed
    if (_udpCommSocket)
LM's avatar
LM committed
    {
Don Gagne's avatar
Don Gagne committed
        _udpCommSocket->close();
Don Gagne's avatar
Don Gagne committed
        _udpCommSocket = NULL;
LM's avatar
LM committed
    }
    emit simulationDisconnected();
    emit simulationConnected(false);
Don Gagne's avatar
Don Gagne committed

    // Exit the thread
    quit();

Don Gagne's avatar
Don Gagne committed
/// @brief Splits a space seperated set of command line arguments into a QStringList.
///         Quoted strings are allowed and handled correctly.
Don Gagne's avatar
Don Gagne committed
///     @param uiArgs Arguments to parse
///     @param argList Returned argument list
Don Gagne's avatar
Don Gagne committed
/// @return Returns false if the argument list has mistmatced quotes within in.
Don Gagne's avatar
Don Gagne committed
bool QGCFlightGearLink::parseUIArguments(QString uiArgs, QStringList& argList)
{
Don Gagne's avatar
Don Gagne committed
    // FYI: The only reason this routine is public is so that we can reference it from a unit test.
Don Gagne's avatar
Don Gagne committed

dogmaphobic's avatar
dogmaphobic committed
    // This is not as easy as it seams since some options can be quoted to preserve spaces within things like
Don Gagne's avatar
Don Gagne committed
    // 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.
dogmaphobic's avatar
dogmaphobic committed

    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"));
dogmaphobic's avatar
dogmaphobic committed
                        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();
    }

Don Gagne's avatar
Don Gagne committed
/// @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
Don Gagne's avatar
Don Gagne committed
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.
 **/
lm's avatar
lm committed
bool QGCFlightGearLink::connectSimulation()
Don Gagne's avatar
Don Gagne committed
    // 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.
dogmaphobic's avatar
dogmaphobic committed

    if (!_vehicle->uas()) {
Don Gagne's avatar
Don Gagne committed
        return false;
    }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    QString     fgAppName;
    QString     fgRootPath;						// FlightGear root data directory as specified by --fg-root
    QStringList fgRootPathProposedList;         // Directories we will attempt to search for --fg-root
dogmaphobic's avatar
dogmaphobic committed
    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
Don Gagne's avatar
Don Gagne committed
    QDir        fgAppDir;						// Location of main FlightGear application

    // 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 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) {
        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;
dogmaphobic's avatar
dogmaphobic committed

#if defined __macos__
Don Gagne's avatar
Don Gagne committed
    // 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/";
    }
Don Gagne's avatar
Don Gagne committed
    fgRootPathProposedList += "/Applications/FlightGear.app/Contents/Resources/data/";
Don Gagne's avatar
Don Gagne committed
#elif defined Q_OS_WIN32
Don Gagne's avatar
Don Gagne committed
    _fgProcessName = "fgfs.exe";
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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);
dogmaphobic's avatar
dogmaphobic committed
            QString lookahead;	// lookahead for continuation lines
Don Gagne's avatar
Don Gagne committed
            while (!in.atEnd() || !lookahead.isEmpty()) {
                QString line;
dogmaphobic's avatar
dogmaphobic committed
                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();
                    }
                }

Don Gagne's avatar
Don Gagne committed
                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));
Don Gagne's avatar
Don Gagne committed
                        _fgProcessWorkingDirPath = fgAppDir.absolutePath();
Don Gagne's avatar
Don Gagne committed
                        qDebug() << "fg_exe" << fgAppDir.absolutePath();
                    }
                    continue;
                }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
                regExp.setPattern("^fg_root:(.*)");
                if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
Don Gagne's avatar
Don Gagne committed
                    fgRootPathProposedList += QDir(regExp.cap(1)).absolutePath();
                    qDebug() << "fg_root" << fgRootPathProposedList[0];
Don Gagne's avatar
Don Gagne committed
                    continue;
                }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
                regExp.setPattern("^fg_scenery:(.*)");
                if (regExp.indexIn(line) == 0 && regExp.captureCount() == 1) {
dogmaphobic's avatar
dogmaphobic committed
                    // Scenery can contain multiple paths seperated by ';' so don't do QDir::absolutePath on it
Don Gagne's avatar
Don Gagne committed
                    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";
Don Gagne's avatar
Don Gagne committed
    _fgProcessName = "fgfs";
Don Gagne's avatar
Don Gagne committed
    fgRootPathProposedList += "/usr/share/flightgear/data/";    // Default Archlinux location
    fgRootPathProposedList += "/usr/share/games/flightgear/";   // Default Ubuntu location
Don Gagne's avatar
Don Gagne committed
#else
#error Unknown OS build flavor
#endif
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
#ifndef Q_OS_LINUX
Don Gagne's avatar
Don Gagne committed
    // Validate the FlightGear application directory location. Linux runs from path so we don't validate on that OS.
Don Gagne's avatar
Don Gagne committed
    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;
        }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
        // Let the user pick the right directory
Don Gagne's avatar
Don Gagne committed
        QString dirPath = QGCFileDialog::getExistingDirectory(MainWindow::instance(), tr("Please select directory of FlightGear application : ") + fgAppName);
Don Gagne's avatar
Don Gagne committed
        if (dirPath.isEmpty()) {
            return false;
        }
        fgAppDir.setPath(dirPath);
Don Gagne's avatar
Don Gagne committed
        fgAppFullyQualified = fgAppDir.absoluteFilePath(fgAppName);
    }
#endif
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // If we have an --fg-root coming in from the ui options, that setting overrides any internal searching of
Don Gagne's avatar
Don Gagne committed
    // proposed locations.
    QString argValue;
    fgRootDirOverride = _findUIArgument(_fgArgList, "--fg-root", argValue);
    if (fgRootDirOverride) {
        fgRootPathProposedList.clear();
        fgRootPathProposedList += argValue;
        qDebug() << "--fg-root override" << argValue;
    }
Don Gagne's avatar
Don Gagne committed
    // 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();
        }
    }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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);
Don Gagne's avatar
Don Gagne committed
        return false;
    }
Don Gagne's avatar
Don Gagne committed
    if (!fgRootDirOverride) {
        _fgArgList += "--fg-root=" + fgRootPath;
    }

Don Gagne's avatar
Don Gagne committed
    // 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.
Don Gagne's avatar
Don Gagne committed
    fgSceneryDirOverride = _findUIArgument(_fgArgList, "--fg-scenery", argValue);
    if (fgSceneryDirOverride) {
        fgSceneryPath = argValue;
        qDebug() << "--fg-scenery override" << argValue;
Don Gagne's avatar
Don Gagne committed
    } else if (!fgSceneryPath.isEmpty()) {
dogmaphobic's avatar
dogmaphobic committed
        _fgArgList += "--fg-scenery=" + fgSceneryPath;
    }

Don Gagne's avatar
Don Gagne committed
#ifdef Q_OS_WIN32
    // Windows won't start without an --fg-scenery set. We don't validate the directory in the path since
dogmaphobic's avatar
dogmaphobic committed
    // it can be multiple paths.
Don Gagne's avatar
Don Gagne committed
    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);
Don Gagne's avatar
Don Gagne committed
        return false;
    }
Don Gagne's avatar
Don Gagne committed
#else
    Q_UNUSED(fgSceneryDirOverride);
Don Gagne's avatar
Don Gagne committed
#endif
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // Setup and verify directory which contains QGC provided aircraft files
Don Gagne's avatar
Don Gagne committed
    QString qgcAircraftDir(QApplication::applicationDirPath() + "/flightgear/Aircraft");
Don Gagne's avatar
Don Gagne committed
    if (!QFileInfo(qgcAircraftDir).isDir()) {
        QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect QGroundControl installation. Aircraft directory is missing: '%1'.").arg(qgcAircraftDir));
Don Gagne's avatar
Don Gagne committed
        return false;
    }
    _fgArgList += "--fg-aircraft=" + qgcAircraftDir;
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // Setup protocol we will be using to communicate with FlightGear
    QString fgProtocol(_vehicle->vehicleType() == MAV_TYPE_QUADROTOR ? "qgroundcontrol-quadrotor" : "qgroundcontrol-fixed-wing");
Don Gagne's avatar
Don Gagne committed
    QString fgProtocolArg("--generic=socket,%1,300,127.0.0.1,%2,udp,%3");
Don Gagne's avatar
Don Gagne committed
    _fgArgList << fgProtocolArg.arg("out").arg(port).arg(fgProtocol);
    _fgArgList << fgProtocolArg.arg("in").arg(currentPort).arg(fgProtocol);
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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()));
Don Gagne's avatar
Don Gagne committed
        return false;
    }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // Verify directory which contains QGC provided FlightGear communication protocol files
Don Gagne's avatar
Don Gagne committed
    QDir qgcProtocolDir(QApplication::applicationDirPath() + "/flightgear/Protocol/");
Don Gagne's avatar
Don Gagne committed
    if (!qgcProtocolDir.isReadable()) {
        QGCMessageBox::critical(tr("FlightGear HIL"), tr("Incorrect QGroundControl installation. Protocol directory is missing (%1).").arg(qgcProtocolDir.path()));
Don Gagne's avatar
Don Gagne committed
        return false;
    }
dogmaphobic's avatar
dogmaphobic committed

    // 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 QGroundControl installation. FlightGear protocol file missing: %1").arg(qgcProtocolFileFullyQualified));
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // 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);
dogmaphobic's avatar
dogmaphobic committed

    if (QFileInfo(_fgProtocolFileFullyQualified).exists()) {
        // Verify that the file is current by comparing it against the one in QGC
dogmaphobic's avatar
dogmaphobic committed

        QFile fgFile(_fgProtocolFileFullyQualified);
        QFile qgcFile(qgcProtocolFileFullyQualified);
dogmaphobic's avatar
dogmaphobic committed

        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 QGroundControl install the latest file.").arg(_fgProtocolFileFullyQualified));
        }
dogmaphobic's avatar
dogmaphobic committed

        QByteArray fgBytes = fgFile.readAll();
        QByteArray qgcBytes = qgcFile.readAll();
dogmaphobic's avatar
dogmaphobic committed

        fgFile.close();
        qgcFile.close();
dogmaphobic's avatar
dogmaphobic committed

        if (fgBytes != qgcBytes) {
            QGCMessageBox::warning(tr("FlightGear HIL"), tr("FlightGear protocol file %1 is out of date. It will be deleted, which will cause QGroundControl to install the latest version of the file.").arg(_fgProtocolFileFullyQualified));
            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;
            }
        }
    }
dogmaphobic's avatar
dogmaphobic committed

    if (!QFileInfo(_fgProtocolFileFullyQualified).exists()) {
Don Gagne's avatar
Don Gagne committed
        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;
        }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
        // 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);
Don Gagne's avatar
Don Gagne committed
        if (!succeeded) {
#ifdef Q_OS_WIN32
            QString copyCmd = QString("copy \"%1\" \"%2\"").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
Don Gagne's avatar
Don Gagne committed
            copyCmd.replace("/", "\\");
#else
            QString copyCmd = QString("sudo cp %1 %2").arg(qgcProtocolFileFullyQualified).arg(_fgProtocolFileFullyQualified);
Don Gagne's avatar
Don Gagne committed
#endif
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
            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,
Don Gagne's avatar
Don Gagne committed
#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,
Don Gagne's avatar
Don Gagne committed
#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;
        }
    }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // Start the engines to save a startup step
    if (_vehicle->vehicleType() == MAV_TYPE_QUADROTOR) {
Don Gagne's avatar
Don Gagne committed
        // 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";
    }
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
    // We start out at our home position
    _fgArgList << QString("--lat=%1").arg(qgcApp()->toolbox()->homePositionManager()->getHomeLatitude());
    _fgArgList << QString("--lon=%1").arg(qgcApp()->toolbox()->homePositionManager()->getHomeLongitude());
Don Gagne's avatar
Don Gagne committed
    // 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(qgcApp()->toolbox()->homePositionManager()->getHomeAltitude());
dogmaphobic's avatar
dogmaphobic committed

Don Gagne's avatar
Don Gagne committed
#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
dogmaphobic's avatar
dogmaphobic committed

    start(HighPriority);
    return true;
Don Gagne's avatar
Don Gagne committed
void QGCFlightGearLink::_printFgfsOutput(void)
Thomas Gubler's avatar
Thomas Gubler committed
{
   qDebug() << "fgfs stdout:";
Don Gagne's avatar
Don Gagne committed
   QByteArray byteArray = _fgProcess->readAllStandardOutput();
Thomas Gubler's avatar
Thomas Gubler committed
   QStringList strLines = QString(byteArray).split("\n");

   foreach (const QString &line, strLines){
Thomas Gubler's avatar
Thomas Gubler committed
    qDebug() << line;
   }
}

Don Gagne's avatar
Don Gagne committed
void QGCFlightGearLink::_printFgfsError(void)
Thomas Gubler's avatar
Thomas Gubler committed
{
   qDebug() << "fgfs stderr:";

Don Gagne's avatar
Don Gagne committed
   QByteArray byteArray = _fgProcess->readAllStandardError();
Thomas Gubler's avatar
Thomas Gubler committed
   QStringList strLines = QString(byteArray).split("\n");

   foreach (const QString &line, strLines){
Thomas Gubler's avatar
Thomas Gubler committed
    qDebug() << line;
   }
}

/**
 * @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()