QGCXPlaneLink.cc 35.9 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

            /* Send flap signals, assuming that they are mapped to channel 5 (ctl_4) */
            sendDataRef("sim/flightmodel/controls/flaprqst", ctl_4);
            sendDataRef("sim/flightmodel/controls/flap2rqst", ctl_4);
539
540
541
542
543
544
545
            break;
        }

    }

}

Bart Slinger's avatar
Bart Slinger committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
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;
573
574
}

575
void QGCXPlaneLink::_writeBytes(const QByteArray data)
576
{
577
    if (data.isEmpty()) return;
578
579
580

    // If socket exists and is connected, transmit the data
    if (socket && connectState)
581
    {
582
        socket->writeDatagram(data, remoteHost, remotePort);
583
584
585
586
    }
}

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

595
    const qint64 maxLength = 65536;
596
597
598
599
600
    char data[maxLength];
    QHostAddress sender;
    quint16 senderPort;

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

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

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

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

621
622
    bool oldConnectionState = xPlaneConnected;

623
624
625
626
627
    if (data[0] == 'D' &&
            data[1] == 'A' &&
            data[2] == 'T' &&
            data[3] == 'A')
    {
628
        xPlaneConnected = true;
629

Bart Slinger's avatar
Bart Slinger committed
630
        if (oldConnectionState != xPlaneConnected) {
631
632
633
            simUpdateFirst = QGC::groundTimeMilliseconds();
        }

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

            if (p.index == 3)
            {
Don Gagne's avatar
Don Gagne committed
642
643
644
645
                float knotsToMetersPerSecond = 0.514444f;
                ind_airspeed = p.f[5] * knotsToMetersPerSecond;
                true_airspeed = p.f[6] * knotsToMetersPerSecond;
                groundspeed = p.f[7] * knotsToMetersPerSecond;
646

647
648
                //qDebug() << "SPEEDS:" << "airspeed" << airspeed << "m/s, groundspeed" << groundspeed << "m/s";
            }
649
650
            if (p.index == 4)
            {
Bart Slinger's avatar
Bart Slinger committed
651
652
653
654
655
656
657
				// 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.
658
                    Eigen::Vector3f g(0, 0, -9.80665f);
Bart Slinger's avatar
Bart Slinger committed
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
					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);
680
                emitUpdate = true;
681
            }
682
            // atmospheric pressure aircraft for XPlane 9 and 10
683
            else if (p.index == 6)
Lorenz Meier's avatar
Lorenz Meier committed
684
            {
685
686
                // inHg to hPa (hecto Pascal / millibar)
                abs_pressure = p.f[0] * 33.863886666718317f;
Lorenz Meier's avatar
Lorenz Meier committed
687
                temperature = p.f[1];
Lorenz Meier's avatar
Lorenz Meier committed
688
                fields_changed |= (1 << 9) | (1 << 12);
Lorenz Meier's avatar
Lorenz Meier committed
689
            }
690
691
692
693
694
695
696
697
698
699
700
            // 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);
//            }
701
            else if ((xPlaneVersion == 10 && p.index == 16) || (xPlaneVersion == 9 && p.index == 17))
702
            {
703
704
705
706
                // Cross checked with XPlane flight
                pitchspeed = p.f[0];
                rollspeed = p.f[1];
                yawspeed = p.f[2];
Lorenz Meier's avatar
Lorenz Meier committed
707
                fields_changed |= (1 << 3) | (1 << 4) | (1 << 5);
708
709

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

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

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

Bart Slinger's avatar
Bart Slinger committed
728
                if (yawmag > M_PI) {
Don Gagne's avatar
Don Gagne committed
729
                    yawmag -= 2.0f * static_cast<float>(M_PI);
Lorenz Meier's avatar
Lorenz Meier committed
730
                }
Bart Slinger's avatar
Bart Slinger committed
731
                if (yawmag < -M_PI) {
Don Gagne's avatar
Don Gagne committed
732
                    yawmag += 2.0f * static_cast<float>(M_PI);
Lorenz Meier's avatar
Lorenz Meier committed
733
734
                }

735
736
737
738
739
740
741
                // 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
742
                fields_changed |= (1 << 6) | (1 << 7) | (1 << 8);
Lorenz Meier's avatar
Lorenz Meier committed
743

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

                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
765
                Eigen::Matrix3f m = Eigen::Map<Eigen::Matrix3f>((float*)dcm).eval();
766
767
768
769
770

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

                Eigen::Vector3f magbody = m * mag;

771
772
//                qDebug() << "yaw mag:" << p.f[2] << "x" << xmag << "y" << ymag;
//                qDebug() << "yaw mag in body:" << magbody(0) << magbody(1) << magbody(2);
773
774
775
776
777
778
779
780

                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
781
                emitUpdate = true;
782
            }
783

784
785
786
787
//            else if (p.index == 19)
//            {
//                qDebug() << "ATT:" << p.f[0] << p.f[1] << p.f[2];
//            }
Bart Slinger's avatar
Bart Slinger committed
788
789
790
791
792
793
794
			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
795
            }
796
            else if (p.index == 21)
Lorenz Meier's avatar
Lorenz Meier committed
797
            {
798
799
                vy = p.f[3];
                vx = -p.f[5];
800
801
802
                // moving 'up' in XPlane is positive, but its negative in NED
                // for us.
                vz = -p.f[4];
Lorenz Meier's avatar
Lorenz Meier committed
803
            }
804
805
            else if (p.index == 12)
            {
806
                //qDebug() << "AIL/ELEV/RUD" << p.f[0] << p.f[1] << p.f[2];
807
808
809
            }
            else if (p.index == 25)
            {
810
                //qDebug() << "THROTTLE" << p.f[0] << p.f[1] << p.f[2] << p.f[3];
811
812
813
            }
            else if (p.index == 0)
            {
814
                //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];
815
816
817
            }
            else if (p.index == 11)
            {
818
                //qDebug() << "CONTROLS" << "ail" << p.f[0] << "elev" << p.f[1] << "rudder" << p.f[2] << "nwheel" << p.f[3];
819
820
821
            }
            else
            {
822
                //qDebug() << "UNKNOWN #" << p.index << p.f[0] << p.f[1] << p.f[2] << p.f[3];
823
824
            }
        }
825
826
827
828
829
830
831
832
833
    }
    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
834
835
836
               data[1] == 'T' &&
               data[2] == 'A' &&
               data[3] == 'T')
837
838
    {

839
840
841
842
843
    }
    else
    {
        qDebug() << "UNKNOWN PACKET:" << data;
    }
844

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

850
    // Send updated state
851
    if (emitUpdate && (QGC::groundTimeMilliseconds() - simUpdateLast) > 2)
Lorenz Meier's avatar
Lorenz Meier committed
852
    {
853
        simUpdateHz = simUpdateHz * 0.9f + 0.1f * (1000.0f / (QGC::groundTimeMilliseconds() - simUpdateLast));
Bart Slinger's avatar
Bart Slinger committed
854
        if (QGC::groundTimeMilliseconds() - simUpdateLastText > 2000) {
855
856
857
858
859
860
861
            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
862
863
        simUpdateLast = QGC::groundTimeMilliseconds();

864
        if (_sensorHilEnabled)
Lorenz Meier's avatar
Lorenz Meier committed
865
        {
866
867
868
869
870
871
872
873
874
875
876
            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;

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

            /*
             * 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
891
892
            // set pressure alt to changed
            fields_changed |= (1 << 11);
Lorenz Meier's avatar
Lorenz Meier committed
893
894

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

Lorenz Meier's avatar
Lorenz Meier committed
897
898
            // XXX make these GUI-configurable and add randomness
            int gps_fix_type = 3;
Don Gagne's avatar
Don Gagne committed
899
900
            float eph = 0.3f;
            float epv = 0.6f;
Bart Slinger's avatar
Bart Slinger committed
901
            float vel = sqrt(vx*vx + vy*vy + vz*vz);
Lorenz Meier's avatar
Lorenz Meier committed
902
903
904
905
            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
906
        } else {
Lorenz Meier's avatar
Lorenz Meier committed
907
            emit hilStateChanged(QGC::groundTimeUsecs(), roll, pitch, yaw, rollspeed,
908
909
910
911
912
                                 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
913
        if (QGC::groundTimeMilliseconds() - simUpdateLastGroundTruth > 40) {
914
915
916
            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
917

918
919
            simUpdateLastGroundTruth = QGC::groundTimeMilliseconds();
        }
Lorenz Meier's avatar
Lorenz Meier committed
920
    }
921

922
923
    if (!oldConnectionState && xPlaneConnected)
    {
924
        emit statusMessage(tr("Receiving from XPlane."));
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
952
953
954
955
    //    // 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()
{
956
    if (connectState)
957
    {
958
        _should_exit = true;
Bart Slinger's avatar
Bart Slinger committed
959
    } else {
960
961
        emit simulationDisconnected();
        emit simulationConnected(false);
962
    }
963
964
965
966

    return !connectState;
}

Bart Slinger's avatar
Bart Slinger committed
967
void QGCXPlaneLink::selectAirframe(const QString& plane)
968
{
969
    airframeName = plane;
970

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

void QGCXPlaneLink::setPositionAttitude(double lat, double lon, double alt, double roll, double pitch, double yaw)
{
Bart Slinger's avatar
Bart Slinger committed
1000
    #pragma pack(push, 1)
For faster browsing, not all history is shown. View entire blame