QGCXPlaneLink.cc 35.7 KB
Newer Older
1
2
3
4
5
6
7
8
/****************************************************************************
 *
 *   (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.
 *
 ****************************************************************************/
9
10
11
12
13


/**
 * @file QGCXPlaneLink.cc
 *   Implementation of X-Plane interface
Lorenz Meier's avatar
Lorenz Meier committed
14
 *   @author Lorenz Meier <lm@qgroundcontrol.org>
15
16
17
18
19
20
21
 *
 */

#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
22
#include <QNetworkInterface>
23
24
#include <QHostInfo>

25
#include <iostream>
26
27
#include <Eigen/Eigen>

28
29
#include "QGCXPlaneLink.h"
#include "QGC.h"
30
#include "UAS.h"
Lorenz Meier's avatar
Lorenz Meier committed
31
#include "UASInterface.h"
32
#include "QGCMessageBox.h"
33

Bart Slinger's avatar
Bart Slinger committed
34
QGCXPlaneLink::QGCXPlaneLink(Vehicle* vehicle, QString remoteHost, QHostAddress localHost, quint16 localPort) :
35
    _vehicle(vehicle),
Lorenz Meier's avatar
Lorenz Meier committed
36
37
    remoteHost(QHostAddress("127.0.0.1")),
    remotePort(49000),
38
    socket(NULL),
39
    process(NULL),
40
    terraSync(NULL),
Lorenz Meier's avatar
Lorenz Meier committed
41
    barometerOffsetkPa(-8.0f),
42
    airframeID(QGCXPlaneLink::AIRFRAME_UNKNOWN),
Lorenz Meier's avatar
Lorenz Meier committed
43
    xPlaneConnected(false),
44
45
    xPlaneVersion(0),
    simUpdateLast(QGC::groundTimeMilliseconds()),
46
    simUpdateFirst(0),
47
    simUpdateLastText(QGC::groundTimeMilliseconds()),
48
    simUpdateLastGroundTruth(QGC::groundTimeMilliseconds()),
Lorenz Meier's avatar
Lorenz Meier committed
49
    simUpdateHz(0),
50
    _sensorHilEnabled(true),
51
    _useHilActuatorControls(true),
52
    _should_exit(false)
53
{
54
55
56
57
    // 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);

58
59
    setTerminationEnabled(false);

Lorenz Meier's avatar
Lorenz Meier committed
60
61
    this->localHost = localHost;
    this->localPort = localPort/*+mav->getUASID()*/;
62
63
    connectState = false;

Lorenz Meier's avatar
Lorenz Meier committed
64
    this->name = tr("X-Plane Link (localPort:%1)").arg(localPort);
65
    setRemoteHost(remoteHost);
Lorenz Meier's avatar
Lorenz Meier committed
66
    loadSettings();
67
68
69
}

QGCXPlaneLink::~QGCXPlaneLink()
70
{
Lorenz Meier's avatar
Lorenz Meier committed
71
    storeSettings();
Lorenz Meier's avatar
Lorenz Meier committed
72
    // Tell the thread to exit
73
    _should_exit = true;
Lorenz Meier's avatar
Lorenz Meier committed
74

Bart Slinger's avatar
Bart Slinger committed
75
    if (socket) {
76
77
78
79
        socket->close();
        socket->deleteLater();
        socket = NULL;
    }
80
81
}

Lorenz Meier's avatar
Lorenz Meier committed
82
83
84
85
86
void QGCXPlaneLink::loadSettings()
{
    // Load defaults from settings
    QSettings settings;
    settings.beginGroup("QGC_XPLANE_LINK");
87
    setRemoteHost(settings.value("REMOTE_HOST", QString("%1:%2").arg(remoteHost.toString()).arg(remotePort)).toString());
Lorenz Meier's avatar
Lorenz Meier committed
88
    setVersion(settings.value("XPLANE_VERSION", 10).toInt());
Lorenz Meier's avatar
Lorenz Meier committed
89
    selectAirframe(settings.value("AIRFRAME", "default").toString());
90
    _sensorHilEnabled = settings.value("SENSOR_HIL", _sensorHilEnabled).toBool();
91
    _useHilActuatorControls = settings.value("ACTUATOR_HIL", _useHilActuatorControls).toBool();
Lorenz Meier's avatar
Lorenz Meier committed
92
93
94
95
96
97
98
99
    settings.endGroup();
}

void QGCXPlaneLink::storeSettings()
{
    // Store settings
    QSettings settings;
    settings.beginGroup("QGC_XPLANE_LINK");
Lorenz Meier's avatar
Lorenz Meier committed
100
    settings.setValue("REMOTE_HOST", QString("%1:%2").arg(remoteHost.toString()).arg(remotePort));
Lorenz Meier's avatar
Lorenz Meier committed
101
102
    settings.setValue("XPLANE_VERSION", xPlaneVersion);
    settings.setValue("AIRFRAME", airframeName);
103
    settings.setValue("SENSOR_HIL", _sensorHilEnabled);
104
    settings.setValue("ACTUATOR_HIL", _useHilActuatorControls);
Lorenz Meier's avatar
Lorenz Meier committed
105
106
107
    settings.endGroup();
}

Bart Slinger's avatar
Bart Slinger committed
108
void QGCXPlaneLink::setVersion(const QString& version)
Lorenz Meier's avatar
Lorenz Meier committed
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
{
    unsigned int oldVersion = xPlaneVersion;
    if (version.contains("9"))
    {
        xPlaneVersion = 9;
    }
    else if (version.contains("10"))
    {
        xPlaneVersion = 10;
    }
    else if (version.contains("11"))
    {
        xPlaneVersion = 11;
    }
    else if (version.contains("12"))
    {
        xPlaneVersion = 12;
    }

    if (oldVersion != xPlaneVersion)
    {
        emit versionChanged(QString("X-Plane %1").arg(xPlaneVersion));
    }
}

Lorenz Meier's avatar
Lorenz Meier committed
134
135
136
137
138
139
void QGCXPlaneLink::setVersion(unsigned int version)
{
    bool changed = (xPlaneVersion != version);
    xPlaneVersion = version;
    if (changed) emit versionChanged(QString("X-Plane %1").arg(xPlaneVersion));
}
Lorenz Meier's avatar
Lorenz Meier committed
140

141
142
void QGCXPlaneLink::enableHilActuatorControls(bool enable)
{
Bart Slinger's avatar
Bart Slinger committed
143
    if (enable != _useHilActuatorControls) {
144
145
146
147
148
149
        _useHilActuatorControls = enable;
    }

    /* Only use override for new message and specific airframes */
    MAV_TYPE type = _vehicle->vehicleType();
    float value = 0.0f;
Bart Slinger's avatar
Bart Slinger committed
150
    if (type == MAV_TYPE_VTOL_RESERVED2) {
151
152
153
154
155
156
157
        value = (enable ? 1.0f : 0.0f);
    }

    sendDataRef("sim/operation/override/override_control_surfaces", value);
    emit useHilActuatorControlsChanged(enable);
}

Lorenz Meier's avatar
Lorenz Meier committed
158

159
160
161
162
163
164
/**
 * @brief Runs the thread
 *
 **/
void QGCXPlaneLink::run()
{
Bart Slinger's avatar
Bart Slinger committed
165
    if (!_vehicle) {
166
167
168
169
        emit statusMessage("No MAV present");
        return;
    }

Bart Slinger's avatar
Bart Slinger committed
170
    if (connectState) {
171
172
173
        emit statusMessage("Already connected");
        return;
    }
174
175

    socket = new QUdpSocket(this);
Lorenz Meier's avatar
Lorenz Meier committed
176
    socket->moveToThread(this);
177
    connectState = socket->bind(localHost, localPort, QAbstractSocket::ReuseAddressHint);
Bart Slinger's avatar
Bart Slinger committed
178
    if (!connectState) {
179
180
181

        emit statusMessage("Binding socket failed!");

182
        socket->deleteLater();
183
184
185
        socket = NULL;
        return;
    }
186

187
188
    emit statusMessage(tr("Waiting for XPlane.."));

189
    QObject::connect(socket, &QUdpSocket::readyRead, this, &QGCXPlaneLink::readBytes);
190

191
    connect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCXPlaneLink::updateControls, Qt::QueuedConnection);
Bart Slinger's avatar
Bart Slinger committed
192
    connect(_vehicle, &Vehicle::hilActuatorControlsChanged, this, &QGCXPlaneLink::updateActuatorControls, Qt::QueuedConnection);
193

194
195
196
197
    connect(this, &QGCXPlaneLink::hilGroundTruthChanged, _vehicle->uas(), &UAS::sendHilGroundTruth, Qt::QueuedConnection);
    connect(this, &QGCXPlaneLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState, Qt::QueuedConnection);
    connect(this, &QGCXPlaneLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps, Qt::QueuedConnection);
    connect(this, &QGCXPlaneLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors, Qt::QueuedConnection);
198

199
    _vehicle->uas()->startHil();
200
201
202
203
204

#pragma pack(push, 1)
    struct iset_struct
    {
        char b[5];
Patrick José Pereira's avatar
Patrick José Pereira committed
205
        int index; // (0->20 in the list below)
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        char str_ipad_them[16];
        char str_port_them[6];
        char padding[2];
        int use_ip;
    } ip; // to use this option, 0 not to.
#pragma pack(pop)

    ip.b[0] = 'I';
    ip.b[1] = 'S';
    ip.b[2] = 'E';
    ip.b[3] = 'T';
    ip.b[4] = '0';

    QList<QHostAddress> hostAddresses = QNetworkInterface::allAddresses();

    QString localAddrStr;
    QString localPortStr = QString("%1").arg(localPort);

    for (int i = 0; i < hostAddresses.size(); i++)
    {
        // Exclude loopback IPv4 and all IPv6 addresses
        if (hostAddresses.at(i) != QHostAddress("127.0.0.1") && !hostAddresses.at(i).toString().contains(":"))
        {
            localAddrStr = hostAddresses.at(i).toString();
            break;
        }
Lorenz Meier's avatar
Lorenz Meier committed
232
    }
233
234

    ip.index = 0;
235
236
    strncpy(ip.str_ipad_them, localAddrStr.toLatin1(), qMin((int)sizeof(ip.str_ipad_them), 16));
    strncpy(ip.str_port_them, localPortStr.toLatin1(), qMin((int)sizeof(ip.str_port_them), 6));
237
238
    ip.use_ip = 1;

Bart Slinger's avatar
Bart Slinger committed
239
    writeBytesSafe((const char*)&ip, sizeof(ip));
240

241
242
243
    /* Call function which makes sure individual control override is enabled/disabled */
    enableHilActuatorControls(_useHilActuatorControls);

244
245
    _should_exit = false;

Bart Slinger's avatar
Bart Slinger committed
246
    while(!_should_exit) {
247
248
249
        QCoreApplication::processEvents();
        QGC::SLEEP::msleep(5);
    }
250

251
    disconnect(_vehicle->uas(), &UAS::hilControlsChanged, this, &QGCXPlaneLink::updateControls);
252

253
254
255
256
    disconnect(this, &QGCXPlaneLink::hilGroundTruthChanged, _vehicle->uas(), &UAS::sendHilGroundTruth);
    disconnect(this, &QGCXPlaneLink::hilStateChanged, _vehicle->uas(), &UAS::sendHilState);
    disconnect(this, &QGCXPlaneLink::sensorHilGpsChanged, _vehicle->uas(), &UAS::sendHilGps);
    disconnect(this, &QGCXPlaneLink::sensorHilRawImuChanged, _vehicle->uas(), &UAS::sendHilSensors);
257
258
    connectState = false;

259
    disconnect(socket, &QUdpSocket::readyRead, this, &QGCXPlaneLink::readBytes);
260

261
    socket->close();
262
    socket->deleteLater();
263
264
265
266
    socket = NULL;

    emit simulationDisconnected();
    emit simulationConnected(false);
267
268
}

Lorenz Meier's avatar
Lorenz Meier committed
269
void QGCXPlaneLink::setPort(int localPort)
270
{
Lorenz Meier's avatar
Lorenz Meier committed
271
    this->localPort = localPort;
272
273
274
275
276
277
    disconnectSimulation();
    connectSimulation();
}

void QGCXPlaneLink::processError(QProcess::ProcessError err)
{
278
    QString msg;
Bart Slinger's avatar
Bart Slinger committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    
    switch(err) {
        case QProcess::FailedToStart:
            msg = tr("X-Plane Failed to start. Please check if the path and command is correct");
            break;
            
        case QProcess::Crashed:
            msg = tr("X-Plane crashed. This is an X-Plane-related problem, check for X-Plane upgrade.");
            break;
            
        case QProcess::Timedout:
            msg = tr("X-Plane start timed out. Please check if the path and command is correct");
            break;
            
        case QProcess::ReadError:
        case QProcess::WriteError:
            msg = tr("Could not communicate with X-Plane. Please check if the path and command are correct");
            break;
            
        case QProcess::UnknownError:
        default:
            msg = tr("X-Plane error occurred. Please check if the path and command is correct.");
            break;
302
    }
Bart Slinger's avatar
Bart Slinger committed
303
304
    
    
305
    QGCMessageBox::critical(tr("X-Plane HIL"), msg);
306
307
}

Lorenz Meier's avatar
Lorenz Meier committed
308
309
QString QGCXPlaneLink::getRemoteHost()
{
Lorenz Meier's avatar
Lorenz Meier committed
310
    return QString("%1:%2").arg(remoteHost.toString()).arg(remotePort);
Lorenz Meier's avatar
Lorenz Meier committed
311
312
}

313
/**
Lorenz Meier's avatar
Lorenz Meier committed
314
 * @param newHost Hostname in standard formatting, e.g. localhost:14551 or 192.168.1.1:14551
315
 */
Bart Slinger's avatar
Bart Slinger committed
316
void QGCXPlaneLink::setRemoteHost(const QString& newHost)
317
{
Lorenz Meier's avatar
Lorenz Meier committed
318
319
320
    if (newHost.length() == 0)
        return;

Lorenz Meier's avatar
Lorenz Meier committed
321
    if (newHost.contains(":"))
322
    {
Lorenz Meier's avatar
Lorenz Meier committed
323
        QHostInfo info = QHostInfo::fromName(newHost.split(":").first());
324
325
        if (info.error() == QHostInfo::NoError)
        {
Lorenz Meier's avatar
Lorenz Meier committed
326
327
            // Add newHost
            QList<QHostAddress> newHostAddresses = info.addresses();
328
            QHostAddress address;
Lorenz Meier's avatar
Lorenz Meier committed
329
            for (int i = 0; i < newHostAddresses.size(); i++)
330
331
            {
                // Exclude loopback IPv4 and all IPv6 addresses
Lorenz Meier's avatar
Lorenz Meier committed
332
                if (!newHostAddresses.at(i).toString().contains(":"))
333
                {
Lorenz Meier's avatar
Lorenz Meier committed
334
                    address = newHostAddresses.at(i);
335
336
                }
            }
Lorenz Meier's avatar
Lorenz Meier committed
337
338
            remoteHost = address;
            // Set localPort according to user input
Lorenz Meier's avatar
Lorenz Meier committed
339
            remotePort = newHost.split(":").last().toInt();
340
341
342
343
        }
    }
    else
    {
Lorenz Meier's avatar
Lorenz Meier committed
344
        QHostInfo info = QHostInfo::fromName(newHost);
345
346
        if (info.error() == QHostInfo::NoError)
        {
Lorenz Meier's avatar
Lorenz Meier committed
347
            // Add newHost
Lorenz Meier's avatar
Lorenz Meier committed
348
            remoteHost = info.addresses().first();
Lorenz Meier's avatar
Lorenz Meier committed
349
            if (remotePort == 0) remotePort = 49000;
350
351
352
        }
    }

Lorenz Meier's avatar
Lorenz Meier committed
353
354
355
356
357
    if (isConnected())
    {
        disconnectSimulation();
        connectSimulation();
    }
358
359
360
361

    emit remoteChanged(QString("%1:%2").arg(remoteHost.toString()).arg(remotePort));
}

362
void QGCXPlaneLink::updateControls(quint64 time, float rollAilerons, float pitchElevator, float yawRudder, float throttle, quint8 systemMode, quint8 navMode)
363
{
364
    /* Only use HIL_CONTROL when the checkbox is unchecked */
Bart Slinger's avatar
Bart Slinger committed
365
    if (_useHilActuatorControls) {
366
367
368
        //qDebug() << "received HIL_CONTROL but not using it";
        return;
    }
Bart Slinger's avatar
Bart Slinger committed
369
370
    #pragma pack(push, 1)
    struct payload {
371
372
373
374
        char b[5];
        int index;
        float f[8];
    } p;
Bart Slinger's avatar
Bart Slinger committed
375
    #pragma pack(pop)
376
377
378
379
380
381
382

    p.b[0] = 'D';
    p.b[1] = 'A';
    p.b[2] = 'T';
    p.b[3] = 'A';
    p.b[4] = '\0';

383
384
385
386
    Q_UNUSED(time);
    Q_UNUSED(systemMode);
    Q_UNUSED(navMode);

Lorenz Meier's avatar
Lorenz Meier committed
387
    if (_vehicle->vehicleType() == MAV_TYPE_QUADROTOR
Bart Slinger's avatar
Bart Slinger committed
388
389
        || _vehicle->vehicleType() == MAV_TYPE_HEXAROTOR
        || _vehicle->vehicleType() == MAV_TYPE_OCTOROTOR)
390
391
392
393
    {
        qDebug() << "MAV_TYPE_QUADROTOR";

        // Individual effort will be provided directly to the actuators on Xplane quadrotor.
394
        p.f[0] = yawRudder;
395
        p.f[1] = rollAilerons;
396
397
398
        p.f[2] = throttle;
        p.f[3] = pitchElevator;

Lorenz Meier's avatar
Lorenz Meier committed
399
400
        // Direct throttle control
        p.index = 25;
Bart Slinger's avatar
Bart Slinger committed
401
        writeBytesSafe((const char*)&p, sizeof(p));
402
    }
Lorenz Meier's avatar
Lorenz Meier committed
403
404
    else
    {
405
        // direct pass-through, normal fixed-wing.
Lorenz Meier's avatar
Lorenz Meier committed
406
407
408
        p.f[0] = -pitchElevator;
        p.f[1] = rollAilerons;
        p.f[2] = yawRudder;
409

410
        // Ail / Elevon / Rudder
Lorenz Meier's avatar
Lorenz Meier committed
411
412
413

        // Send to group 12
        p.index = 12;
Bart Slinger's avatar
Bart Slinger committed
414
        writeBytesSafe((const char*)&p, sizeof(p));
415

Lorenz Meier's avatar
Lorenz Meier committed
416
417
        // Send to group 8, which equals manual controls
        p.index = 8;
Bart Slinger's avatar
Bart Slinger committed
418
        writeBytesSafe((const char*)&p, sizeof(p));
419

Lorenz Meier's avatar
Lorenz Meier committed
420
421
        // Send throttle to all four motors
        p.index = 25;
422
423
424
425
426
        memset(p.f, 0, sizeof(p.f));
        p.f[0] = throttle;
        p.f[1] = throttle;
        p.f[2] = throttle;
        p.f[3] = throttle;
Bart Slinger's avatar
Bart Slinger committed
427
        writeBytesSafe((const char*)&p, sizeof(p));
428
    }
429
430
}

431
432
void QGCXPlaneLink::updateActuatorControls(quint64 time, quint64 flags, float ctl_0, float ctl_1, float ctl_2, float ctl_3, float ctl_4, float ctl_5, float ctl_6, float ctl_7, float ctl_8, float ctl_9, float ctl_10, float ctl_11, float ctl_12, float ctl_13, float ctl_14, float ctl_15, quint8 mode)
{
Bart Slinger's avatar
Bart Slinger committed
433
    if (!_useHilActuatorControls) {
434
435
436
437
438
439
440
441
442
443
444
445
        //qDebug() << "received HIL_ACTUATOR_CONTROLS but not using it";
        return;
    }

    Q_UNUSED(time);
    Q_UNUSED(flags);
    Q_UNUSED(mode);
    Q_UNUSED(ctl_12);
    Q_UNUSED(ctl_13);
    Q_UNUSED(ctl_14);
    Q_UNUSED(ctl_15);

Bart Slinger's avatar
Bart Slinger committed
446
447
    #pragma pack(push, 1)
    struct payload {
448
449
450
451
        char b[5];
        int index;
        float f[8];
    } p;
Bart Slinger's avatar
Bart Slinger committed
452
    #pragma pack(pop)
453
454
455
456
457
458
459
460
461
462

    p.b[0] = 'D';
    p.b[1] = 'A';
    p.b[2] = 'T';
    p.b[3] = 'A';
    p.b[4] = '\0';

    /* Initialize with zeroes */
    memset(p.f, 0, sizeof(p.f));

Bart Slinger's avatar
Bart Slinger committed
463
464
465
466
    switch (_vehicle->vehicleType()) {
        case MAV_TYPE_QUADROTOR:
        case MAV_TYPE_HEXAROTOR:
        case MAV_TYPE_OCTOROTOR:
467
468
469
470
471
472
473
474
475
476
477
478
        {
            p.f[0] = ctl_0;         ///< X-Plane Engine 1
            p.f[1] = ctl_1;         ///< X-Plane Engine 2
            p.f[2] = ctl_2;         ///< X-Plane Engine 3
            p.f[3] = ctl_3;         ///< X-Plane Engine 4
            p.f[4] = ctl_4;         ///< X-Plane Engine 5
            p.f[5] = ctl_5;         ///< X-Plane Engine 6
            p.f[6] = ctl_6;         ///< X-Plane Engine 7
            p.f[7] = ctl_7;         ///< X-Plane Engine 8

            /* Direct throttle control */
            p.index = 25;
Bart Slinger's avatar
Bart Slinger committed
479
            writeBytesSafe((const char*)&p, sizeof(p));
480
481
            break;
        }
Bart Slinger's avatar
Bart Slinger committed
482
        case MAV_TYPE_VTOL_RESERVED2:
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
        {
            /**
             * Tailsitter with four control flaps and eight motors.
             */

            /* Throttle channels */
            p.f[0] = ctl_0;
            p.f[1] = ctl_1;
            p.f[2] = ctl_2;
            p.f[3] = ctl_3;
            p.f[4] = ctl_4;
            p.f[5] = ctl_5;
            p.f[6] = ctl_6;
            p.f[7] = ctl_7;
            p.index = 25;
Bart Slinger's avatar
Bart Slinger committed
498
            writeBytesSafe((const char*)&p, sizeof(p));
499
500
501
502
503
504
505

            /* Control individual actuators */
            float max_surface_deflection = 30.0f; // Degrees
            sendDataRef("sim/flightmodel/controls/wing1l_ail1def", ctl_8 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing1r_ail1def", ctl_9 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing2l_ail1def", ctl_10 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing2r_ail1def", ctl_11 * max_surface_deflection);
Bart Slinger's avatar
Bart Slinger committed
506
507
508
509
            sendDataRef("sim/flightmodel/controls/wing1l_ail2def", ctl_12 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing1r_ail2def", ctl_13 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing2l_ail2def", ctl_14 * max_surface_deflection);
            sendDataRef("sim/flightmodel/controls/wing2r_ail2def", ctl_15 * max_surface_deflection);
510
511
512

            break;
        }
Bart Slinger's avatar
Bart Slinger committed
513
        default:
514
515
516
517
518
519
520
521
        {
            /* direct pass-through, normal fixed-wing. */
            p.f[0] = -ctl_1;        ///< X-Plane Elevator
            p.f[1] = ctl_0;         ///< X-Plane Aileron
            p.f[2] = ctl_2;         ///< X-Plane Rudder

            /* Send to group 8, which equals manual controls */
            p.index = 8;
Bart Slinger's avatar
Bart Slinger committed
522
            writeBytesSafe((const char*)&p, sizeof(p));
523
524
525
526
527
528
529
530
531
532
533

            /* Send throttle to all eight motors */
            p.index = 25;
            p.f[0] = ctl_3;
            p.f[1] = ctl_3;
            p.f[2] = ctl_3;
            p.f[3] = ctl_3;
            p.f[4] = ctl_3;
            p.f[5] = ctl_3;
            p.f[6] = ctl_3;
            p.f[7] = ctl_3;
Bart Slinger's avatar
Bart Slinger committed
534
            writeBytesSafe((const char*)&p, sizeof(p));
535
536
537
538
539
540
541
            break;
        }

    }

}

Bart Slinger's avatar
Bart Slinger committed
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
Eigen::Matrix3f euler_to_wRo(double yaw, double pitch, double roll) {
  double c__ = cos(yaw);
  double _c_ = cos(pitch);
  double __c = cos(roll);
  double s__ = sin(yaw);
  double _s_ = sin(pitch);
  double __s = sin(roll);
  double cc_ = c__ * _c_;
  double cs_ = c__ * _s_;
  double sc_ = s__ * _c_;
  double ss_ = s__ * _s_;
  double c_c = c__ * __c;
  double c_s = c__ * __s;
  double s_c = s__ * __c;
  double s_s = s__ * __s;
  double _cc = _c_ * __c;
  double _cs = _c_ * __s;
  double csc = cs_ * __c;
  double css = cs_ * __s;
  double ssc = ss_ * __c;
  double sss = ss_ * __s;
  Eigen::Matrix3f wRo;
  wRo <<
    cc_  , css-s_c,  csc+s_s,
    sc_  , sss+c_c,  ssc-c_s,
    -_s_  ,     _cs,      _cc;
  return wRo;
569
570
}

571
void QGCXPlaneLink::_writeBytes(const QByteArray data)
572
{
573
    if (data.isEmpty()) return;
574
575
576

    // If socket exists and is connected, transmit the data
    if (socket && connectState)
577
    {
578
        socket->writeDatagram(data, remoteHost, remotePort);
579
580
581
582
    }
}

/**
583
 * @brief Read all pending packets from the interface.
584
585
586
 **/
void QGCXPlaneLink::readBytes()
{
Lorenz Meier's avatar
Lorenz Meier committed
587
588
    // Only emit updates on attitude message
    bool emitUpdate = false;
Lorenz Meier's avatar
Lorenz Meier committed
589
    quint16 fields_changed = 0;
Lorenz Meier's avatar
Lorenz Meier committed
590

591
    const qint64 maxLength = 65536;
592
593
594
595
596
    char data[maxLength];
    QHostAddress sender;
    quint16 senderPort;

    unsigned int s = socket->pendingDatagramSize();
597
    if (s > maxLength) std::cerr << __FILE__ << __LINE__ << " UDP datagram overflow, allowed to read less bytes than datagram size: " << s << std::endl;
598
    socket->readDatagram(data, maxLength, &sender, &senderPort);
Bart Slinger's avatar
Bart Slinger committed
599
600
601
    if (s > maxLength) {
    	std::string headStr = std::string(data, data+5);
    	std::cerr << __FILE__ << __LINE__ << " UDP datagram header: " << headStr << std::endl;
602
    }
603

Lorenz Meier's avatar
Lorenz Meier committed
604
605
    // Calculate the number of data segments a 36 bytes
    // XPlane always has 5 bytes header: 'DATA@'
Bart Slinger's avatar
Bart Slinger committed
606
    unsigned nsegs = (s-5)/36;
607

608
    //qDebug() << "XPLANE:" << "LEN:" << s << "segs:" << nsegs;
Lorenz Meier's avatar
Lorenz Meier committed
609

Bart Slinger's avatar
Bart Slinger committed
610
611
    #pragma pack(push, 1)
    struct payload {
612
613
614
        int index;
        float f[8];
    } p;
Bart Slinger's avatar
Bart Slinger committed
615
    #pragma pack(pop)
Lorenz Meier's avatar
Lorenz Meier committed
616

617
618
    bool oldConnectionState = xPlaneConnected;

619
620
621
622
623
    if (data[0] == 'D' &&
            data[1] == 'A' &&
            data[2] == 'T' &&
            data[3] == 'A')
    {
624
        xPlaneConnected = true;
625

Bart Slinger's avatar
Bart Slinger committed
626
        if (oldConnectionState != xPlaneConnected) {
627
628
629
            simUpdateFirst = QGC::groundTimeMilliseconds();
        }

630
631
632
        for (unsigned i = 0; i < nsegs; i++)
        {
            // Get index
Bart Slinger's avatar
Bart Slinger committed
633
634
            unsigned ioff = (5+i*36);;
            memcpy(&(p), data+ioff, sizeof(p));
635
636
637

            if (p.index == 3)
            {
Don Gagne's avatar
Don Gagne committed
638
639
640
641
                float knotsToMetersPerSecond = 0.514444f;
                ind_airspeed = p.f[5] * knotsToMetersPerSecond;
                true_airspeed = p.f[6] * knotsToMetersPerSecond;
                groundspeed = p.f[7] * knotsToMetersPerSecond;
642

643
644
                //qDebug() << "SPEEDS:" << "airspeed" << airspeed << "m/s, groundspeed" << groundspeed << "m/s";
            }
645
646
            if (p.index == 4)
            {
Bart Slinger's avatar
Bart Slinger committed
647
648
649
650
651
652
653
				// WORKAROUND: IF ground speed <<1m/s and altitude-above-ground <1m, do NOT use the X-Plane data, because X-Plane (tested 
				// with v10.3 and earlier) delivers yacc=0 and zacc=0 when the ground speed is very low, which gives e.g. wrong readings 
				// before launch when waiting on the runway. This might pose a problem for initial state estimation/calibration. 
				// Instead, we calculate our own accelerations.
				if (fabsf(groundspeed)<0.1f && alt_agl<1.0) 
				{
					// TODO: Add centrip. acceleration to the current static acceleration implementation.
654
                    Eigen::Vector3f g(0, 0, -9.80665f);
Bart Slinger's avatar
Bart Slinger committed
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
					Eigen::Matrix3f R = euler_to_wRo(yaw, pitch, roll);
					Eigen::Vector3f gr = R.transpose().eval() * g;

					xacc = gr[0];
					yacc = gr[1];
					zacc = gr[2];

					//qDebug() << "Calculated values" << gr[0] << gr[1] << gr[2];
				}
				else
				{
					// Accelerometer readings, directly from X-Plane and including centripetal forces. 
					const float one_g = 9.80665f;
					xacc = p.f[5] * one_g;
					yacc = p.f[6] * one_g;
					zacc = -p.f[4] * one_g;

					//qDebug() << "X-Plane values" << xacc << yacc << zacc;
				}

				fields_changed |= (1 << 0) | (1 << 1) | (1 << 2);
676
                emitUpdate = true;
677
            }
678
            // atmospheric pressure aircraft for XPlane 9 and 10
679
            else if (p.index == 6)
Lorenz Meier's avatar
Lorenz Meier committed
680
            {
681
682
                // inHg to hPa (hecto Pascal / millibar)
                abs_pressure = p.f[0] * 33.863886666718317f;
Lorenz Meier's avatar
Lorenz Meier committed
683
                temperature = p.f[1];
Lorenz Meier's avatar
Lorenz Meier committed
684
                fields_changed |= (1 << 9) | (1 << 12);
Lorenz Meier's avatar
Lorenz Meier committed
685
            }
686
687
688
689
690
691
692
693
694
695
696
            // Forward controls from X-Plane to MAV, not very useful
            // better: Connect Joystick to QGroundControl
//            else if (p.index == 8)
//            {
//                //qDebug() << "MAN:" << p.f[0] << p.f[3] << p.f[7];
//                man_roll = p.f[0];
//                man_pitch = p.f[1];
//                man_yaw = p.f[2];
//                UAS* uas = dynamic_cast<UAS*>(mav);
//                if (uas) uas->setManualControlCommands(man_roll, man_pitch, man_yaw, 0.6);
//            }
697
            else if ((xPlaneVersion == 10 && p.index == 16) || (xPlaneVersion == 9 && p.index == 17))
698
            {
699
700
701
702
                // Cross checked with XPlane flight
                pitchspeed = p.f[0];
                rollspeed = p.f[1];
                yawspeed = p.f[2];
Lorenz Meier's avatar
Lorenz Meier committed
703
                fields_changed |= (1 << 3) | (1 << 4) | (1 << 5);
704
705

                emitUpdate = true;
706
            }
Lorenz Meier's avatar
Lorenz Meier committed
707
            else if ((xPlaneVersion == 10 && p.index == 17) || (xPlaneVersion == 9 && p.index == 18))
708
            {
709
                //qDebug() << "HDNG" << "pitch" << p.f[0] << "roll" << p.f[1] << "hding true" << p.f[2] << "hding mag" << p.f[3];
710
711
                pitch = p.f[0] / 180.0f * M_PI;
                roll = p.f[1] / 180.0f * M_PI;
712
                yaw = p.f[2] / 180.0f * M_PI;
713

714
                // X-Plane expresses yaw as 0..2 PI
Bart Slinger's avatar
Bart Slinger committed
715
                if (yaw > M_PI) {
Don Gagne's avatar
Don Gagne committed
716
                    yaw -= 2.0f * static_cast<float>(M_PI);
717
                }
Bart Slinger's avatar
Bart Slinger committed
718
                if (yaw < -M_PI) {
Don Gagne's avatar
Don Gagne committed
719
                    yaw += 2.0f * static_cast<float>(M_PI);
720
                }
Lorenz Meier's avatar
Lorenz Meier committed
721

722
                float yawmag = p.f[3] / 180.0f * M_PI;
Lorenz Meier's avatar
Lorenz Meier committed
723

Bart Slinger's avatar
Bart Slinger committed
724
                if (yawmag > M_PI) {
Don Gagne's avatar
Don Gagne committed
725
                    yawmag -= 2.0f * static_cast<float>(M_PI);
Lorenz Meier's avatar
Lorenz Meier committed
726
                }
Bart Slinger's avatar
Bart Slinger committed
727
                if (yawmag < -M_PI) {
Don Gagne's avatar
Don Gagne committed
728
                    yawmag += 2.0f * static_cast<float>(M_PI);
Lorenz Meier's avatar
Lorenz Meier committed
729
730
                }

731
732
733
734
735
736
737
                // Normal rotation matrix, but since we rotate the
                // vector [0.25 0 0.45]', we end up with these relevant
                // matrix parts.

                xmag = cos(-yawmag) * 0.25f;
                ymag = sin(-yawmag) * 0.25f;
                zmag = 0.45f;
Lorenz Meier's avatar
Lorenz Meier committed
738
                fields_changed |= (1 << 6) | (1 << 7) | (1 << 8);
Lorenz Meier's avatar
Lorenz Meier committed
739

740
741
742
743
                double cosPhi = cos(roll);
                double sinPhi = sin(roll);
                double cosThe = cos(pitch);
                double sinThe = sin(pitch);
Lorenz Meier's avatar
Lorenz Meier committed
744
745
                double cosPsi = cos(0.0);
                double sinPsi = sin(0.0);
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760

                float dcm[3][3];

                dcm[0][0] = cosThe * cosPsi;
                dcm[0][1] = -cosPhi * sinPsi + sinPhi * sinThe * cosPsi;
                dcm[0][2] = sinPhi * sinPsi + cosPhi * sinThe * cosPsi;

                dcm[1][0] = cosThe * sinPsi;
                dcm[1][1] = cosPhi * cosPsi + sinPhi * sinThe * sinPsi;
                dcm[1][2] = -sinPhi * cosPsi + cosPhi * sinThe * sinPsi;

                dcm[2][0] = -sinThe;
                dcm[2][1] = sinPhi * cosThe;
                dcm[2][2] = cosPhi * cosThe;

Bart Slinger's avatar
Bart Slinger committed
761
                Eigen::Matrix3f m = Eigen::Map<Eigen::Matrix3f>((float*)dcm).eval();
762
763
764
765
766

                Eigen::Vector3f mag(xmag, ymag, zmag);

                Eigen::Vector3f magbody = m * mag;

767
768
//                qDebug() << "yaw mag:" << p.f[2] << "x" << xmag << "y" << ymag;
//                qDebug() << "yaw mag in body:" << magbody(0) << magbody(1) << magbody(2);
769
770
771
772
773
774
775
776

                xmag = magbody(0);
                ymag = magbody(1);
                zmag = magbody(2);

                // Rotate the measurement vector into the body frame using roll and pitch


Lorenz Meier's avatar
Lorenz Meier committed
777
                emitUpdate = true;
778
            }
779

780
781
782
783
//            else if (p.index == 19)
//            {
//                qDebug() << "ATT:" << p.f[0] << p.f[1] << p.f[2];
//            }
Bart Slinger's avatar
Bart Slinger committed
784
785
786
787
788
789
790
			else if (p.index == 20)
			{
				//qDebug() << "LAT/LON/ALT:" << p.f[0] << p.f[1] << p.f[2];
				lat = p.f[0];
				lon = p.f[1];
				alt = p.f[2] * 0.3048f; // convert feet (MSL) to meters
				alt_agl = p.f[3] * 0.3048f; //convert feet (AGL) to meters
791
            }
792
            else if (p.index == 21)
Lorenz Meier's avatar
Lorenz Meier committed
793
            {
794
795
                vy = p.f[3];
                vx = -p.f[5];
796
797
798
                // moving 'up' in XPlane is positive, but its negative in NED
                // for us.
                vz = -p.f[4];
Lorenz Meier's avatar
Lorenz Meier committed
799
            }
800
801
            else if (p.index == 12)
            {
802
                //qDebug() << "AIL/ELEV/RUD" << p.f[0] << p.f[1] << p.f[2];
803
804
805
            }
            else if (p.index == 25)
            {
806
                //qDebug() << "THROTTLE" << p.f[0] << p.f[1] << p.f[2] << p.f[3];
807
808
809
            }
            else if (p.index == 0)
            {
810
                //qDebug() << "STATS" << "fgraphics/s" << p.f[0] << "fsim/s" << p.f[2] << "t frame" << p.f[3] << "cpu load" << p.f[4] << "grnd ratio" << p.f[5] << "filt ratio" << p.f[6];
811
812
813
            }
            else if (p.index == 11)
            {
814
                //qDebug() << "CONTROLS" << "ail" << p.f[0] << "elev" << p.f[1] << "rudder" << p.f[2] << "nwheel" << p.f[3];
815
816
817
            }
            else
            {
818
                //qDebug() << "UNKNOWN #" << p.index << p.f[0] << p.f[1] << p.f[2] << p.f[3];
819
820
            }
        }
821
822
823
824
825
826
827
828
829
    }
    else if (data[0] == 'S' &&
             data[1] == 'N' &&
             data[2] == 'A' &&
             data[3] == 'P')
    {

    }
    else if (data[0] == 'S' &&
Bart Slinger's avatar
Bart Slinger committed
830
831
832
               data[1] == 'T' &&
               data[2] == 'A' &&
               data[3] == 'T')
833
834
    {

835
836
837
838
839
    }
    else
    {
        qDebug() << "UNKNOWN PACKET:" << data;
    }
840

841
    // Wait for 0.5s before actually using the data, so that all fields are filled
Bart Slinger's avatar
Bart Slinger committed
842
    if (QGC::groundTimeMilliseconds() - simUpdateFirst < 500) {
843
844
845
        return;
    }

846
    // Send updated state
847
    if (emitUpdate && (QGC::groundTimeMilliseconds() - simUpdateLast) > 2)
Lorenz Meier's avatar
Lorenz Meier committed
848
    {
849
        simUpdateHz = simUpdateHz * 0.9f + 0.1f * (1000.0f / (QGC::groundTimeMilliseconds() - simUpdateLast));
Bart Slinger's avatar
Bart Slinger committed
850
        if (QGC::groundTimeMilliseconds() - simUpdateLastText > 2000) {
851
852
853
854
855
856
857
            emit statusMessage(tr("Receiving from XPlane at %1 Hz").arg(static_cast<int>(simUpdateHz)));
            // Reset lowpass with current value
            simUpdateHz = (1000.0f / (QGC::groundTimeMilliseconds() - simUpdateLast));
            // Set state
            simUpdateLastText = QGC::groundTimeMilliseconds();
        }

Lorenz Meier's avatar
Lorenz Meier committed
858
859
        simUpdateLast = QGC::groundTimeMilliseconds();

860
        if (_sensorHilEnabled)
Lorenz Meier's avatar
Lorenz Meier committed
861
        {
862
863
864
865
866
867
868
869
870
871
872
            diff_pressure = (ind_airspeed * ind_airspeed * 1.225f) / 2.0f;

            /* tropospheric properties (0-11km) for standard atmosphere */
            const double T1 = 15.0 + 273.15;	/* temperature at base height in Kelvin */
            const double a  = -6.5 / 1000;	/* temperature gradient in degrees per metre */
            const double g  = 9.80665;	/* gravity constant in m/s/s */
            const double R  = 287.05;	/* ideal gas constant in J/kg/K */

            /* current pressure at MSL in kPa */
            double p1 = 1013.25 / 10.0;

873
874
            /* measured pressure in hPa, plus offset to simulate weather effects / offsets */
            double p = abs_pressure / 10.0 + barometerOffsetkPa;
875
876
877
878
879
880
881
882
883
884
885
886

            /*
             * Solve:
             *
             *     /        -(aR / g)     \
             *    | (p / p1)          . T1 | - T1
             *     \                      /
             * h = -------------------------------  + h1
             *                   a
             */
            pressure_alt = (((pow((p / p1), (-(a * R) / g))) * T1) - T1) / a;

Lorenz Meier's avatar
Lorenz Meier committed
887
888
            // set pressure alt to changed
            fields_changed |= (1 << 11);
Lorenz Meier's avatar
Lorenz Meier committed
889
890

            emit sensorHilRawImuChanged(QGC::groundTimeUsecs(), xacc, yacc, zacc, rollspeed, pitchspeed, yawspeed,
891
                                        xmag, ymag, zmag, abs_pressure, diff_pressure / 100.0, pressure_alt, temperature, fields_changed);
892

Lorenz Meier's avatar
Lorenz Meier committed
893
894
            // XXX make these GUI-configurable and add randomness
            int gps_fix_type = 3;
Don Gagne's avatar
Don Gagne committed
895
896
            float eph = 0.3f;
            float epv = 0.6f;
Bart Slinger's avatar
Bart Slinger committed
897
            float vel = sqrt(vx*vx + vy*vy + vz*vz);
Lorenz Meier's avatar
Lorenz Meier committed
898
899
900
901
            float cog = atan2(vy, vx);
            int satellites = 8;

            emit sensorHilGpsChanged(QGC::groundTimeUsecs(), lat, lon, alt, gps_fix_type, eph, epv, vel, vx, vy, vz, cog, satellites);
Bart Slinger's avatar
Bart Slinger committed
902
        } else {
Lorenz Meier's avatar
Lorenz Meier committed
903
            emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
904
905
906
907
908
                                 pitchspeed, yawspeed, lat, lon, alt,
                                 vx, vy, vz, ind_airspeed, true_airspeed, xacc, yacc, zacc);
        }

        // Limit ground truth to 25 Hz
Bart Slinger's avatar
Bart Slinger committed
909
        if (QGC::groundTimeMilliseconds() - simUpdateLastGroundTruth > 40) {
910
911
912
            emit hilGroundTruthChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
                                       pitchspeed, yawspeed, lat, lon, alt,
                                       vx, vy, vz, ind_airspeed, true_airspeed, xacc, yacc, zacc);
Lorenz Meier's avatar
Lorenz Meier committed
913

914
915
            simUpdateLastGroundTruth = QGC::groundTimeMilliseconds();
        }
Lorenz Meier's avatar
Lorenz Meier committed
916
    }
917

918
919
    if (!oldConnectionState && xPlaneConnected)
    {
920
        emit statusMessage(tr("Receiving from XPlane."));
921
922
    }

923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
    //    // 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 QGCXPlaneLink::bytesAvailable()
{
    return socket->pendingDatagramSize();
}

/**
 * @brief Disconnect the connection.
 *
 * @return True if connection has been disconnected, false if connection couldn't be disconnected.
 **/
bool QGCXPlaneLink::disconnectSimulation()
{
952
    if (connectState)
953
    {
954
        _should_exit = true;
Bart Slinger's avatar
Bart Slinger committed
955
    } else {
956
957
        emit simulationDisconnected();
        emit simulationConnected(false);
958
    }
959
960
961
962

    return !connectState;
}

Bart Slinger's avatar
Bart Slinger committed
963
void QGCXPlaneLink::selectAirframe(const QString& plane)
964
{
965
    airframeName = plane;
966

967
968
    if (plane.contains("QRO"))
    {
Lorenz Meier's avatar
Lorenz Meier committed
969
        if (plane.contains("MK") && airframeID != AIRFRAME_QUAD_X_MK_10INCH_I2C)
970
971
        {
            airframeID = AIRFRAME_QUAD_X_MK_10INCH_I2C;
Lorenz Meier's avatar
Lorenz Meier committed
972
            emit airframeChanged("QRO_X / MK");
973
        }
Lorenz Meier's avatar
Lorenz Meier committed
974
        else if (plane.contains("ARDRONE") && airframeID != AIRFRAME_QUAD_X_ARDRONE)
975
976
        {
            airframeID = AIRFRAME_QUAD_X_ARDRONE;
Lorenz Meier's avatar
Lorenz Meier committed
977
            emit airframeChanged("QRO_X / ARDRONE");
978
979
980
        }
        else
        {
Lorenz Meier's avatar
Lorenz Meier committed
981
            bool changed = (airframeID != AIRFRAME_QUAD_DJI_F450_PWM);
982
            airframeID = AIRFRAME_QUAD_DJI_F450_PWM;
Lorenz Meier's avatar
Lorenz Meier committed
983
            if (changed) emit airframeChanged("QRO_X / DJI-F450 / PWM");
984
985
        }
    }
Lorenz Meier's avatar
Lorenz Meier committed
986
987
    else
    {
Lorenz Meier's avatar
Lorenz Meier committed
988
        bool changed = (airframeID != AIRFRAME_UNKNOWN);
Lorenz Meier's avatar
Lorenz Meier committed
989
        airframeID = AIRFRAME_UNKNOWN;
Lorenz Meier's avatar
Lorenz Meier committed
990
        if (changed) emit airframeChanged("X Plane default");
Lorenz Meier's avatar
Lorenz Meier committed
991
    }
992
993
994
995
}

void QGCXPlaneLink::setPositionAttitude(double lat, double lon, double alt, double roll, double pitch, double yaw)
{
Bart Slinger's avatar
Bart Slinger committed
996
    #pragma pack(push, 1)
997
998
    struct VEH1_struct
    {
999
        char header[5];
1000
1001
1002
1003
1004
        quint32 p;
        double lat_lon_ele[3];
        float psi_the_phi[3];
        float gear_flap_vect[3];
    } pos;
Bart Slinger's avatar
Bart Slinger committed
1005
    #pragma pack(pop)
1006

1007
1008
1009
1010
1011
    pos.header[0] = 'V';
    pos.header[1] = 'E';
    pos.header[2] = 'H';
    pos.header[3] = '1';
    pos.header[4] = '0';
1012
1013
1014
1015
    pos.p = 0;
    pos.lat_lon_ele[0] = lat;
    pos.lat_lon_ele[1] = lon;
    pos.lat_lon_ele[2] = alt;
1016
1017
1018
1019
1020
1021
1022
    pos.psi_the_phi[0] = roll;
    pos.psi_the_phi[1] = pitch;
    pos.psi_the_phi[2] = yaw;
    pos.gear_flap_vect[0] = 0.0f;
    pos.gear_flap_vect[1] = 0.0f;
    pos.gear_flap_vect[2] = 0.0f;

Bart Slinger's avatar
Bart Slinger committed
1023
    writeBytesSafe((const char*)&pos, sizeof(pos));
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040

//    pos.header[0] = 'V';
//    pos.header[1] = 'E';
//    pos.header[2] = 'H';
//    pos.header[3] = '1';
//    pos.header[4] = '0';
//    pos.p = 0;
//    pos.lat_lon_ele[0] = -999;
//    pos.lat_lon_ele[1] = -999;
//    pos.lat_lon_ele[2] = -999;
//    pos.psi_the_phi[0] = -999;
//    pos.psi_the_phi[1] = -999;
//    pos.psi_the_phi[2] = -999;
//    pos.gear_flap_vect[0] = -999;
//    pos.gear_flap_vect[1] = -999;
//    pos.gear_flap_vect[2] = -999;

1041
//    writeBytesSafe((const char*)&pos, sizeof(pos));
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
}

/**
 * Sets a random position with an offset of max 1/1000 degree
 * and max 100 m altitude
 */
void QGCXPlaneLink::setRandomPosition()
{
    // Initialize generator
    srand(0);

Bart Slinger's avatar
Bart Slinger committed
1053
1054
    double offLat = rand() / static_cast<double>(RAND_MAX) / 500.0 + 1.0/500.0;
    double offLon = rand() / static_cast<double>(RAND_MAX) / 500.0 + 1.0/500.0;
1055
1056
    double offAlt = rand() / static_cast<double>(RAND_MAX) * 200.0 + 100.0;

Don Gagne's avatar
Don Gagne committed
1057
    if (_vehicle->altitudeAMSL()->rawValue().toDouble() + offAlt < 0)
1058
1059
1060
1061
    {
        offAlt *= -1.0;
    }

1062
1063
    setPositionAttitude(_vehicle->latitude() + offLat,
                        _vehicle->longitude() + offLon,
Don Gagne's avatar
Don Gagne committed
1064
1065
1066
                        _vehicle->altitudeAMSL()->rawValue().toDouble() + offAlt,
                        _vehicle->roll()->rawValue().toDouble(),
                        _vehicle->pitch()->rawValue().toDouble(),
1067
                        _vehicle->uas()->getYaw());
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
}

void QGCXPlaneLink::setRandomAttitude()
{
    // Initialize generator
    srand(0);

    double roll = rand() / static_cast<double>(RAND_MAX) * 2.0 - 1.0;
    double pitch = rand() / static_cast<double>(RAND_MAX) * 2.0 - 1.0;
    double yaw = rand() / static_cast<double>(RAND_MAX) * 2.0 - 1.0;

1079
1080
    setPositionAttitude(_vehicle->latitude(),
                        _vehicle->longitude(),
Don Gagne's avatar
Don Gagne committed
1081
                        _vehicle->altitudeAMSL()->rawValue().toDouble(),
1082
1083
1084
1085
1086
                        roll,
                        pitch,
                        yaw);
}

1087
1088
1089
1090
1091
1092
1093
/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
bool QGCXPlaneLink::connectSimulation()
{
Bart Slinger's avatar
Bart Slinger committed
1094
    if (connectState) {
1095
        qDebug() << "Simulation already active";
Bart Slinger's avatar
Bart Slinger committed
1096
    } else {
1097
1098
1099
1100
1101
1102
        qDebug() << "STARTING X-PLANE LINK, CONNECTING TO" << remoteHost << ":" << remotePort;
        // XXX Hack
        storeSettings();

        start(HighPriority);
    }
1103

1104
    return true;
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
}

/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
bool QGCXPlaneLink::isConnected()
{
    return connectState;
}

QString QGCXPlaneLink::getName()
{
    return name;
}

void QGCXPlaneLink::setName(QString name)
{
    this->name = name;
    //    emit nameChanged(this->name);
}
1127
1128
1129

void QGCXPlaneLink::sendDataRef(QString ref, float value)
{
Bart Slinger's avatar
Bart Slinger committed
1130
1131
    #pragma pack(push, 1)
    struct payload {
1132
1133
1134
1135
        char b[5];
        float value;
        char name[500];
    } dref;
Bart Slinger's avatar
Bart Slinger committed
1136
    #pragma pack(pop)
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153

    dref.b[0] = 'D';
    dref.b[1] = 'R';
    dref.b[2] = 'E';
    dref.b[3] = 'F';
    dref.b[4] = '0';

    /* Set value */
    dref.value = value;

    /* Fill name with zeroes */
    memset(dref.name, 0, sizeof(dref.name));

    /* Set dref name */

    /* Send command */
    QByteArray ba = ref.toUtf8();
Bart Slinger's avatar
Bart Slinger committed
1154
    if (ba.length() > 500) {
1155
1156
1157
        return;
    }

Bart Slinger's avatar
Bart Slinger committed
1158
    for (int i = 0; i < ba.length(); i++) {
1159
1160
        dref.name[i] = ba.at(i);
    }
Bart Slinger's avatar
Bart Slinger committed
1161
    writeBytesSafe((const char*)&dref, sizeof(dref));
1162
}