Skip to content
Snippets Groups Projects
QGCFlightGearLink.cc 41.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*=====================================================================
    
    QGroundControl Open Source Ground Control Station
    
    (c) 2009 - 2011 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/>.
    
    ======================================================================*/
    
    /**
     * @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;