MAVLinkLogManager.cc 29.2 KB
Newer Older
Gus Grubba's avatar
Gus Grubba committed
1 2 3 4 5 6 7 8 9
/****************************************************************************
 *
 *   (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.
 *
 ****************************************************************************/

Gus Grubba's avatar
Gus Grubba committed
10
#include "MAVLinkLogManager.h"
Gus Grubba's avatar
Gus Grubba committed
11 12 13 14 15 16 17 18 19 20 21
#include "QGCApplication.h"
#include <QQmlContext>
#include <QQmlProperty>
#include <QQmlEngine>
#include <QtQml>
#include <QSettings>
#include <QHttpPart>
#include <QNetworkReply>
#include <QFile>
#include <QFileInfo>

22 23
#define kTimeOutMilliseconds 1000

Gus Grubba's avatar
Gus Grubba committed
24
QGC_LOGGING_CATEGORY(MAVLinkLogManagerLog, "MAVLinkLogManagerLog")
Gus Grubba's avatar
Gus Grubba committed
25

26 27
static const char* kEmailAddressKey         = "MAVLinkLogEmail";
static const char* kDescriptionsKey         = "MAVLinkLogDescription";
28
static const char* kDefaultDescr            = "QGroundControl Session";
29
static const char* kPx4URLKey               = "MAVLinkLogURL";
30 31 32
static const char* kDefaultPx4URL           = "http://logs.px4.io/upload";
static const char* kEnableAutoUploadKey     = "EnableAutoUploadKey";
static const char* kEnableAutoStartKey      = "EnableAutoStartKey";
33
static const char* kEnableDeletetKey        = "EnableDeleteKey";
Gus Grubba's avatar
Gus Grubba committed
34 35
static const char* kUlogExtension           = ".ulg";
static const char* kSidecarExtension        = ".uploaded";
Gus Grubba's avatar
Gus Grubba committed
36 37

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
38
MAVLinkLogFiles::MAVLinkLogFiles(MAVLinkLogManager* manager, const QString& filePath, bool newFile)
Gus Grubba's avatar
Gus Grubba committed
39 40 41 42 43
    : _manager(manager)
    , _size(0)
    , _selected(false)
    , _uploading(false)
    , _progress(0)
Gus Grubba's avatar
Gus Grubba committed
44
    , _writing(false)
Gus Grubba's avatar
Gus Grubba committed
45
    , _uploaded(false)
Gus Grubba's avatar
Gus Grubba committed
46 47 48
{
    QFileInfo fi(filePath);
    _name = fi.baseName();
Gus Grubba's avatar
Gus Grubba committed
49 50 51 52 53 54 55
    if(!newFile) {
        _size = (quint32)fi.size();
        QString sideCar = filePath;
        sideCar.replace(kUlogExtension, kSidecarExtension);
        QFileInfo sc(sideCar);
        _uploaded = sc.exists();
    }
Gus Grubba's avatar
Gus Grubba committed
56 57
}

58 59
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
60
MAVLinkLogFiles::setSize(quint32 size)
61 62 63 64 65
{
    _size = size;
    emit sizeChanged();
}

Gus Grubba's avatar
Gus Grubba committed
66 67
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
68
MAVLinkLogFiles::setSelected(bool selected)
Gus Grubba's avatar
Gus Grubba committed
69 70 71 72 73 74 75 76
{
    _selected = selected;
    emit selectedChanged();
    emit _manager->selectedCountChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
77
MAVLinkLogFiles::setUploading(bool uploading)
Gus Grubba's avatar
Gus Grubba committed
78 79 80 81 82 83 84
{
    _uploading = uploading;
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
85
MAVLinkLogFiles::setProgress(qreal progress)
Gus Grubba's avatar
Gus Grubba committed
86 87 88 89 90
{
    _progress = progress;
    emit progressChanged();
}

91 92
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
93
MAVLinkLogFiles::setWriting(bool writing)
94 95 96 97 98
{
    _writing = writing;
    emit writingChanged();
}

Gus Grubba's avatar
Gus Grubba committed
99 100
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
101
MAVLinkLogFiles::setUploaded(bool uploaded)
Gus Grubba's avatar
Gus Grubba committed
102 103 104 105 106
{
    _uploaded = uploaded;
    emit uploadedChanged();
}

Gus Grubba's avatar
Gus Grubba committed
107 108
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
109
MAVLinkLogProcessor::MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
110 111 112 113 114 115 116
    : _fd(NULL)
    , _written(0)
    , _sequence(-1)
    , _numDrops(0)
    , _gotHeader(false)
    , _error(false)
    , _record(NULL)
Gus Grubba's avatar
Gus Grubba committed
117
{
Gus Grubba's avatar
Gus Grubba committed
118 119 120
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
121
MAVLinkLogProcessor::~MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
122 123 124 125 126 127
{
    close();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
128
MAVLinkLogProcessor::close()
Gus Grubba's avatar
Gus Grubba committed
129 130 131 132
{
    if(_fd) {
        fclose(_fd);
        _fd = NULL;
Gus Grubba's avatar
Gus Grubba committed
133 134 135
    }
}

Gus Grubba's avatar
Gus Grubba committed
136 137
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
138
MAVLinkLogProcessor::valid()
Gus Grubba's avatar
Gus Grubba committed
139 140 141 142 143 144
{
    return (_fd != NULL) && (_record != NULL);
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
145
MAVLinkLogProcessor::create(MAVLinkLogManager* manager, const QString path, uint8_t id)
Gus Grubba's avatar
Gus Grubba committed
146 147 148 149 150 151 152 153
{
    _fileName.sprintf("%s/%03d-%s%s",
        path.toLatin1().data(),
        id,
        QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data(),
        kUlogExtension);
    _fd = fopen(_fileName.toLatin1().data(), "wb");
    if(_fd) {
Gus Grubba's avatar
Gus Grubba committed
154
        _record = new MAVLinkLogFiles(manager, _fileName, true);
Gus Grubba's avatar
Gus Grubba committed
155 156 157 158 159 160 161 162 163
        _record->setWriting(true);
        _sequence = -1;
        return true;
    }
    return false;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
164
MAVLinkLogProcessor::_checkSequence(uint16_t seq, int& num_drops)
Gus Grubba's avatar
Gus Grubba committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
{
    num_drops = 0;
    //-- Check if a sequence is newer than the one previously received and if
    //   there were dropped messages between the last one and this.
    if(_sequence == -1) {
        _sequence = seq;
        return true;
    }
    if((uint16_t)_sequence == seq) {
        return false;
    }
    if(seq > (uint16_t)_sequence) {
        // Account for wrap-arounds, sequence is 2 bytes
        if((seq - _sequence) > (1 << 15)) { // Assume reordered
            return false;
        }
        num_drops = seq - _sequence - 1;
        _numDrops += num_drops;
        _sequence = seq;
        return true;
    } else {
        if((_sequence - seq) > (1 << 15)) {
            num_drops = (1 << 16) - _sequence - 1 + seq;
            _numDrops += num_drops;
            _sequence = seq;
            return true;
        }
        return false;
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
198
MAVLinkLogProcessor::_writeData(void* data, int len)
Gus Grubba's avatar
Gus Grubba committed
199 200 201 202 203 204 205 206 207
{
    if(!_error) {
        _error = fwrite(data, 1, len, _fd) != (size_t)len;
        if(!_error) {
            _written += len;
            if(_record) {
                _record->setSize(_written);
            }
        } else {
Gus Grubba's avatar
Gus Grubba committed
208
            qCDebug(MAVLinkLogManagerLog) << "File IO error:" << len << "bytes into" << _fileName;
Gus Grubba's avatar
Gus Grubba committed
209 210 211 212 213 214
        }
    }
}

//-----------------------------------------------------------------------------
QByteArray
Gus Grubba's avatar
Gus Grubba committed
215
MAVLinkLogProcessor::_writeUlogMessage(QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
{
    //-- Write ulog data w/o integrity checking, assuming data starts with a
    //   valid ulog message. returns the remaining data at the end.
    while(data.length() > 2) {
        uint8_t* ptr = (uint8_t*)data.data();
        int message_length = ptr[0] + (ptr[1] * 256) + 3; // 3 = ULog msg header
        if(message_length > data.length())
            break;
        _writeData(data.data(), message_length);
        data.remove(0, message_length);
    }
    return data;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
232
MAVLinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
Gus Grubba's avatar
Gus Grubba committed
233 234 235 236 237 238 239 240
{
    int num_drops = 0;
    _error = false;
    while(_checkSequence(sequence, num_drops)) {
        //-- The first 16 bytes need special treatment (this sounds awfully brittle)
        if(!_gotHeader) {
            if(data.size() < 16) {
                //-- Shouldn't happen but if it does, we might as well close shop.
Gus Grubba's avatar
Gus Grubba committed
241
                qCCritical(MAVLinkLogManagerLog) << "Corrupt log header. Canceling log download.";
Gus Grubba's avatar
Gus Grubba committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
                return false;
            }
            //-- Write header
            _writeData(data.data(), 16);
            data.remove(0, 16);
            _gotHeader = true;
            // What about data start offset now that we removed 16 bytes off the start?
        }
        if(_gotHeader && num_drops > 0) {
            if(num_drops > 25) num_drops = 25;
            //-- Hocus Pocus
            //   Write a dropout message. We don't really know the actual duration,
            //   so just use the number of drops * 10 ms
            uint8_t bogus[] = {2, 0, 79, 0, 0};
            bogus[3] = num_drops * 10;
            _writeData(bogus, sizeof(bogus));
        }
        if(num_drops > 0) {
            _writeUlogMessage(_ulogMessage);
            _ulogMessage.clear();
            //-- If no usefull information in this message. Drop it.
            if(first_message == 255) {
                break;
            }
            if(first_message > 0) {
                data.remove(0, first_message);
                first_message = 0;
            }
        }
        if(first_message == 255 && _ulogMessage.length() > 0) {
            _ulogMessage.append(data);
            break;
        }
        if(_ulogMessage.length()) {
            _writeData(_ulogMessage.data(), _ulogMessage.length());
            if(first_message) {
                _writeData(data.left(first_message).data(), first_message);
            }
            _ulogMessage.clear();
        }
        if(first_message) {
            data.remove(0, first_message);
        }
        _ulogMessage = _writeUlogMessage(data);
        break;
    }
    return !_error;
}

Gus Grubba's avatar
Gus Grubba committed
291
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
292
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
293
MAVLinkLogManager::MAVLinkLogManager(QGCApplication* app)
Gus Grubba's avatar
Gus Grubba committed
294
    : QGCTool(app)
295 296
    , _enableAutoUpload(true)
    , _enableAutoStart(true)
Gus Grubba's avatar
Gus Grubba committed
297 298
    , _nam(NULL)
    , _currentLogfile(NULL)
299 300
    , _vehicle(NULL)
    , _logRunning(false)
Gus Grubba's avatar
Gus Grubba committed
301
    , _loggingDisabled(false)
Gus Grubba's avatar
Gus Grubba committed
302
    , _logProcessor(NULL)
303
    , _deleteAfterUpload(false)
304
    , _loggingCmdTryCount(0)
Gus Grubba's avatar
Gus Grubba committed
305 306 307 308 309 310
{
    //-- Get saved settings
    QSettings settings;
    setEmailAddress(settings.value(kEmailAddressKey, QString()).toString());
    setDescription(settings.value(kDescriptionsKey, QString(kDefaultDescr)).toString());
    setUploadURL(settings.value(kPx4URLKey, QString(kDefaultPx4URL)).toString());
311 312
    setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
    setEnableAutoStart(settings.value(kEnableAutoStartKey, true).toBool());
313
    setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
Gus Grubba's avatar
Gus Grubba committed
314 315
    //-- Logging location
    _logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
Gus Grubba's avatar
Gus Grubba committed
316
    _logPath += "/MAVLinkLogs";
Gus Grubba's avatar
Gus Grubba committed
317
    if(!QDir(_logPath).exists()) {
Gus Grubba's avatar
Gus Grubba committed
318
        if(!QDir().mkpath(_logPath)) {
Gus Grubba's avatar
Gus Grubba committed
319
            qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log download path:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
320
            _loggingDisabled = true;
Gus Grubba's avatar
Gus Grubba committed
321 322
        }
    }
Gus Grubba's avatar
Gus Grubba committed
323 324
    if(!_loggingDisabled) {
        //-- Load current list of logs
Gus Grubba's avatar
Gus Grubba committed
325 326 327
        QString filter = "*";
        filter += kUlogExtension;
        QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
Gus Grubba's avatar
Gus Grubba committed
328
        while(it.hasNext()) {
Gus Grubba's avatar
Gus Grubba committed
329
            _insertNewLog(new MAVLinkLogFiles(this, it.next()));
Gus Grubba's avatar
Gus Grubba committed
330
        }
Gus Grubba's avatar
Gus Grubba committed
331
        qCDebug(MAVLinkLogManagerLog) << "MAVLink logs directory:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
332 333 334 335
    }
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
336
MAVLinkLogManager::~MAVLinkLogManager()
Gus Grubba's avatar
Gus Grubba committed
337 338 339 340 341 342
{
    _logFiles.clear();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
343
MAVLinkLogManager::setToolbox(QGCToolbox* toolbox)
Gus Grubba's avatar
Gus Grubba committed
344
{
Gus Grubba's avatar
Gus Grubba committed
345 346
    QGCTool::setToolbox(toolbox);
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
Gus Grubba's avatar
Gus Grubba committed
347
    qmlRegisterUncreatableType<MAVLinkLogManager>("QGroundControl.MAVLinkLogManager", 1, 0, "MAVLinkLogManager", "Reference only");
Gus Grubba's avatar
Gus Grubba committed
348
    if(!_loggingDisabled) {
Gus Grubba's avatar
Gus Grubba committed
349 350
        connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkLogManager::_activeVehicleChanged);
        connect(&_ackTimer, &QTimer::timeout, this, &MAVLinkLogManager::_processCmdAck);
Gus Grubba's avatar
Gus Grubba committed
351
    }
Gus Grubba's avatar
Gus Grubba committed
352 353 354 355
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
356
MAVLinkLogManager::setEmailAddress(QString email)
Gus Grubba's avatar
Gus Grubba committed
357 358 359 360 361 362 363 364 365
{
    _emailAddress = email;
    QSettings settings;
    settings.setValue(kEmailAddressKey, email);
    emit emailAddressChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
366
MAVLinkLogManager::setDescription(QString description)
Gus Grubba's avatar
Gus Grubba committed
367 368 369 370 371 372 373 374 375
{
    _description = description;
    QSettings settings;
    settings.setValue(kDescriptionsKey, description);
    emit descriptionChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
376
MAVLinkLogManager::setUploadURL(QString url)
Gus Grubba's avatar
Gus Grubba committed
377 378 379 380 381 382 383 384 385 386 387 388
{
    _uploadURL = url;
    if(_uploadURL.isEmpty()) {
        _uploadURL = kDefaultPx4URL;
    }
    QSettings settings;
    settings.setValue(kPx4URLKey, _uploadURL);
    emit uploadURLChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
389
MAVLinkLogManager::setEnableAutoUpload(bool enable)
390 391 392 393 394 395 396 397 398
{
    _enableAutoUpload = enable;
    QSettings settings;
    settings.setValue(kEnableAutoUploadKey, enable);
    emit enableAutoUploadChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
399
MAVLinkLogManager::setEnableAutoStart(bool enable)
Gus Grubba's avatar
Gus Grubba committed
400
{
401
    _enableAutoStart = enable;
Gus Grubba's avatar
Gus Grubba committed
402
    QSettings settings;
403 404
    settings.setValue(kEnableAutoStartKey, enable);
    emit enableAutoStartChanged();
Gus Grubba's avatar
Gus Grubba committed
405 406
}

407 408
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
409
MAVLinkLogManager::setDeleteAfterUpload(bool enable)
410 411 412 413 414 415 416
{
    _deleteAfterUpload = enable;
    QSettings settings;
    settings.setValue(kEnableDeletetKey, enable);
    emit deleteAfterUploadChanged();
}

Gus Grubba's avatar
Gus Grubba committed
417 418
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
419
MAVLinkLogManager::uploading()
Gus Grubba's avatar
Gus Grubba committed
420 421 422 423 424 425
{
    return _currentLogfile != NULL;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
426
MAVLinkLogManager::uploadLog()
Gus Grubba's avatar
Gus Grubba committed
427 428 429 430
{
    if(_currentLogfile) {
        _currentLogfile->setUploading(false);
    }
Gus Grubba's avatar
Gus Grubba committed
431
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
432
        _currentLogfile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
433 434 435
        Q_ASSERT(_currentLogfile);
        if(_currentLogfile->selected()) {
            _currentLogfile->setSelected(false);
Gus Grubba's avatar
Gus Grubba committed
436 437 438 439 440 441 442 443
            if(!_currentLogfile->uploaded() && !_emailAddress.isEmpty() && !_uploadURL.isEmpty()) {
                _currentLogfile->setUploading(true);
                _currentLogfile->setProgress(0.0);
                QString filePath = _makeFilename(_currentLogfile->name());
                _sendLog(filePath);
                emit uploadingChanged();
                return;
            }
Gus Grubba's avatar
Gus Grubba committed
444 445 446
        }
    }
    _currentLogfile = NULL;
447 448 449 450 451
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
452
MAVLinkLogManager::_insertNewLog(MAVLinkLogFiles* newLog)
453 454 455 456 457 458 459
{
    //-- Simpler than trying to sort this thing
    int count = _logFiles.count();
    if(!count) {
        _logFiles.append(newLog);
    } else {
        for(int i = 0; i < count; i++) {
Gus Grubba's avatar
Gus Grubba committed
460
            MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
461 462 463 464 465 466 467
            if(newLog->name() < f->name()) {
                _logFiles.insert(i, newLog);
                return;
            }
        }
        _logFiles.append(newLog);
    }
Gus Grubba's avatar
Gus Grubba committed
468 469
}

Gus Grubba's avatar
Gus Grubba committed
470 471
//-----------------------------------------------------------------------------
int
Gus Grubba's avatar
Gus Grubba committed
472
MAVLinkLogManager::_getFirstSelected()
Gus Grubba's avatar
Gus Grubba committed
473 474
{
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
475
        MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
476 477 478 479 480 481 482 483
        Q_ASSERT(f);
        if(f->selected()) {
            return i;
        }
    }
    return -1;
}

Gus Grubba's avatar
Gus Grubba committed
484 485
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
486
MAVLinkLogManager::deleteLog()
Gus Grubba's avatar
Gus Grubba committed
487
{
Gus Grubba's avatar
Gus Grubba committed
488 489 490 491 492
    while (true) {
        int idx = _getFirstSelected();
        if(idx < 0) {
            break;
        }
Gus Grubba's avatar
Gus Grubba committed
493
        MAVLinkLogFiles* log = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(idx));
494
        _deleteLog(log);
Gus Grubba's avatar
Gus Grubba committed
495
    }
Gus Grubba's avatar
Gus Grubba committed
496 497
}

498 499
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
500
MAVLinkLogManager::_deleteLog(MAVLinkLogFiles* log)
501
{
Gus Grubba's avatar
Gus Grubba committed
502
    QString filePath = _makeFilename(log->name());
503 504
    QFile gone(filePath);
    if(!gone.remove()) {
Gus Grubba's avatar
Gus Grubba committed
505
        qCWarning(MAVLinkLogManagerLog) << "Could not delete MAVLink log file:" << _logPath;
506
    }
Gus Grubba's avatar
Gus Grubba committed
507 508 509 510 511 512 513
    //-- Remove sidecar file (if any)
    filePath.replace(kUlogExtension, kSidecarExtension);
    QFile sgone(filePath);
    if(sgone.exists()) {
        sgone.remove();
    }
    //-- Remove file from list and delete record
514 515 516 517 518
    _logFiles.removeOne(log);
    delete log;
    emit logFilesChanged();
}

Gus Grubba's avatar
Gus Grubba committed
519 520
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
521
MAVLinkLogManager::cancelUpload()
Gus Grubba's avatar
Gus Grubba committed
522
{
Gus Grubba's avatar
Gus Grubba committed
523
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
524
        MAVLinkLogFiles* pLogFile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
525 526 527 528 529 530 531 532 533 534
        Q_ASSERT(pLogFile);
        if(pLogFile->selected() && pLogFile != _currentLogfile) {
            pLogFile->setSelected(false);
        }
    }
    if(_currentLogfile) {
        emit abortUpload();
    }
}

535 536
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
537
MAVLinkLogManager::startLogging()
538 539
{
    if(_vehicle) {
Gus Grubba's avatar
Gus Grubba committed
540 541 542
        if(_createNewLog()) {
            _vehicle->startMavlinkLog();
            _logRunning = true;
543 544
            _loggingCmdTryCount = 0;
            _ackTimer.start(kTimeOutMilliseconds);
Gus Grubba's avatar
Gus Grubba committed
545 546
            emit logRunningChanged();
        }
547 548 549 550 551
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
552
MAVLinkLogManager::stopLogging()
553 554
{
    if(_vehicle) {
555
        //-- Tell vehicle to stop sending logs
556
        _vehicle->stopMavlinkLog();
557
    }
Gus Grubba's avatar
Gus Grubba committed
558 559 560 561
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _logProcessor->record()->setWriting(false);
562 563
            if(_enableAutoUpload) {
                //-- Queue log for auto upload (set selected flag)
Gus Grubba's avatar
Gus Grubba committed
564
                _logProcessor->record()->setSelected(true);
565 566
                if(!uploading()) {
                    uploadLog();
567
                }
Gus Grubba's avatar
Gus Grubba committed
568 569
            }
        }
Gus Grubba's avatar
Gus Grubba committed
570 571
        delete _logProcessor;
        _logProcessor = NULL;
572 573 574 575 576 577 578
        _logRunning = false;
        if(_vehicle) {
            //-- Setup a timer to make sure vehicle received the command
            _loggingCmdTryCount = 0;
            _ackTimer.start(kTimeOutMilliseconds);
        }
        emit logRunningChanged();
579 580 581
    }
}

Gus Grubba's avatar
Gus Grubba committed
582 583 584 585 586 587 588 589 590 591 592 593
//-----------------------------------------------------------------------------
QHttpPart
create_form_part(const QString& name, const QString& value)
{
    QHttpPart formPart;
    formPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(name));
    formPart.setBody(value.toUtf8());
    return formPart;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
594
MAVLinkLogManager::_sendLog(const QString& logFile)
Gus Grubba's avatar
Gus Grubba committed
595 596 597
{
    QString defaultDescription = _description;
    if(_description.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
598
        qCWarning(MAVLinkLogManagerLog) << "Log description missing. Using defaults.";
Gus Grubba's avatar
Gus Grubba committed
599 600 601
        defaultDescription = kDefaultDescr;
    }
    if(_emailAddress.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
602
        qCCritical(MAVLinkLogManagerLog) << "User email missing.";
Gus Grubba's avatar
Gus Grubba committed
603 604 605
        return false;
    }
    if(_uploadURL.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
606
        qCCritical(MAVLinkLogManagerLog) << "Upload URL missing.";
Gus Grubba's avatar
Gus Grubba committed
607 608 609 610
        return false;
    }
    QFileInfo fi(logFile);
    if(!fi.exists()) {
Gus Grubba's avatar
Gus Grubba committed
611
        qCCritical(MAVLinkLogManagerLog) << "Log file missing:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
612 613
        return false;
    }
Gus Grubba's avatar
Gus Grubba committed
614
    QFile* file = new QFile(logFile);
Gus Grubba's avatar
Gus Grubba committed
615
    if(!file || !file->open(QIODevice::ReadOnly)) {
Gus Grubba's avatar
Gus Grubba committed
616 617 618
        if(file) {
            delete file;
        }
Gus Grubba's avatar
Gus Grubba committed
619
        qCCritical(MAVLinkLogManagerLog) << "Could not open log file:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
620 621 622
        return false;
    }
    if(!_nam) {
Gus Grubba's avatar
Gus Grubba committed
623
        _nam = new QNetworkAccessManager(this);
Gus Grubba's avatar
Gus Grubba committed
624 625 626 627 628 629
    }
    QNetworkProxy savedProxy = _nam->proxy();
    QNetworkProxy tempProxy;
    tempProxy.setType(QNetworkProxy::DefaultProxy);
    _nam->setProxy(tempProxy);
    //-- Build POST request
Gus Grubba's avatar
Gus Grubba committed
630
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
Gus Grubba's avatar
Gus Grubba committed
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
    QHttpPart emailPart = create_form_part("email", _emailAddress);
    QHttpPart descriptionPart = create_form_part("description", _description);
    QHttpPart sourcePart = create_form_part("source", "QGroundControl");
    QHttpPart versionPart = create_form_part("version", _app->applicationVersion());
    QHttpPart logPart;
    logPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
    logPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"filearg\"; filename=\"%1\"").arg(fi.fileName()));
    logPart.setBodyDevice(file);
    //-- Assemble request and POST it
    multiPart->append(emailPart);
    multiPart->append(descriptionPart);
    multiPart->append(sourcePart);
    multiPart->append(versionPart);
    multiPart->append(logPart);
    file->setParent(multiPart);
    QNetworkRequest request(_uploadURL);
647
#if QT_VERSION > 0x050600
Gus Grubba's avatar
Gus Grubba committed
648
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
649
#endif
Gus Grubba's avatar
Gus Grubba committed
650
    QNetworkReply* reply = _nam->post(request, multiPart);
Gus Grubba's avatar
Gus Grubba committed
651 652 653 654
    connect(reply, &QNetworkReply::finished,  this, &MAVLinkLogManager::_uploadFinished);
    connect(this, &MAVLinkLogManager::abortUpload, reply, &QNetworkReply::abort);
    //connect(reply, &QNetworkReply::readyRead, this, &MAVLinkLogManager::_dataAvailable);
    connect(reply, &QNetworkReply::uploadProgress, this, &MAVLinkLogManager::_uploadProgress);
Gus Grubba's avatar
Gus Grubba committed
655
    multiPart->setParent(reply);
Gus Grubba's avatar
Gus Grubba committed
656
    qCDebug(MAVLinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
Gus Grubba's avatar
Gus Grubba committed
657 658 659 660 661 662
    _nam->setProxy(savedProxy);
    return true;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
663
MAVLinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
664
{
Gus Grubba's avatar
Gus Grubba committed
665
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
666 667 668 669 670 671
    emit readyRead(data);
    return http_code == 200;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
672
MAVLinkLogManager::_dataAvailable()
Gus Grubba's avatar
Gus Grubba committed
673
{
Gus Grubba's avatar
Gus Grubba committed
674
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
675 676 677 678
    if(!reply) {
        return;
    }
    QByteArray data = reply->readAll();
Gus Grubba's avatar
Gus Grubba committed
679
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
680 681 682 683
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
684
MAVLinkLogManager::_uploadFinished()
Gus Grubba's avatar
Gus Grubba committed
685
{
Gus Grubba's avatar
Gus Grubba committed
686
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
687 688 689 690 691 692
    if(!reply) {
        return;
    }
    const int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    QByteArray data = reply->readAll();
    if(_processUploadResponse(http_code, data)) {
Gus Grubba's avatar
Gus Grubba committed
693
        qCDebug(MAVLinkLogManagerLog) << "Log uploaded.";
Gus Grubba's avatar
Gus Grubba committed
694
        emit succeed();
695 696 697 698 699
        if(_deleteAfterUpload) {
            if(_currentLogfile) {
                _deleteLog(_currentLogfile);
                _currentLogfile = NULL;
            }
Gus Grubba's avatar
Gus Grubba committed
700 701 702 703 704 705 706 707 708 709 710
        } else {
            if(_currentLogfile) {
                _currentLogfile->setUploaded(true);
                //-- Write side-car file to flag it as uploaded
                QString sideCar = _makeFilename(_currentLogfile->name());
                sideCar.replace(kUlogExtension, kSidecarExtension);
                FILE* f = fopen(sideCar.toLatin1().data(), "wb");
                if(f) {
                    fclose(f);
                }
            }
711
        }
Gus Grubba's avatar
Gus Grubba committed
712
    } else {
Gus Grubba's avatar
Gus Grubba committed
713
        qCWarning(MAVLinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
Gus Grubba's avatar
Gus Grubba committed
714 715 716 717 718 719 720 721 722
        emit failed();
    }
    reply->deleteLater();
    //-- Next (if any)
    uploadLog();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
723
MAVLinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
Gus Grubba's avatar
Gus Grubba committed
724 725 726
{
    if(bytesTotal) {
        qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
727
        if(_currentLogfile) {
Gus Grubba's avatar
Gus Grubba committed
728
            _currentLogfile->setProgress(progress);
Gus Grubba's avatar
Gus Grubba committed
729
        }
Gus Grubba's avatar
Gus Grubba committed
730
    }
Gus Grubba's avatar
Gus Grubba committed
731
    qCDebug(MAVLinkLogManagerLog) << bytesSent << "of" << bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
732
}
733 734 735

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
736
MAVLinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
737 738 739 740 741
{
    //-- TODO: This is not quite right. This is being used to detect when a vehicle
    //   connects/disconnects. In reality, if QGC is connected to multiple vehicles,
    //   this is called each time the user switches from one vehicle to another. So
    //   far, I'm working on the assumption that multiple vehicles is a rare exception.
Gus Grubba's avatar
Gus Grubba committed
742
    //   For now, we only handle one log download at a time.
743
    // Disconnect the previous one (if any)
Gus Grubba's avatar
Gus Grubba committed
744
    if(_vehicle) {
Gus Grubba's avatar
Gus Grubba committed
745 746 747
        disconnect(_vehicle, &Vehicle::armedChanged,   this, &MAVLinkLogManager::_armedChanged);
        disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MAVLinkLogManager::_mavlinkLogData);
        disconnect(_vehicle, &Vehicle::commandLongAck, this, &MAVLinkLogManager::_commandLongAck);
748
        _vehicle = NULL;
749 750
        //-- Stop logging (if that's the case)
        stopLogging();
751 752 753
        emit canStartLogChanged();
    }
    // Connect new system
Gus Grubba's avatar
Gus Grubba committed
754
    if(vehicle) {
755
        _vehicle = vehicle;
Gus Grubba's avatar
Gus Grubba committed
756 757 758
        connect(_vehicle, &Vehicle::armedChanged,   this, &MAVLinkLogManager::_armedChanged);
        connect(_vehicle, &Vehicle::mavlinkLogData, this, &MAVLinkLogManager::_mavlinkLogData);
        connect(_vehicle, &Vehicle::commandLongAck, this, &MAVLinkLogManager::_commandLongAck);
759 760 761 762
        emit canStartLogChanged();
    }
}

763 764
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
765
MAVLinkLogManager::_processCmdAck()
766 767 768 769 770
{
    if(_loggingCmdTryCount++ > 3) {
        _ackTimer.stop();
        //-- Give up
        if(_logRunning) {
Gus Grubba's avatar
Gus Grubba committed
771
            qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command had no response.";
772 773
            _discardLog();
        } else {
Gus Grubba's avatar
Gus Grubba committed
774
            qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command had no response.";
775 776 777 778 779
        }
    } else {
        if(_vehicle) {
            if(_logRunning) {
                _vehicle->startMavlinkLog();
Gus Grubba's avatar
Gus Grubba committed
780
                qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command sent again.";
781 782
            } else {
                _vehicle->stopMavlinkLog();
Gus Grubba's avatar
Gus Grubba committed
783
                qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command sent again.";
784 785 786 787 788 789 790 791 792
            }
            _ackTimer.start(kTimeOutMilliseconds);
        } else {
            //-- Vehicle went away on us
            _ackTimer.stop();
        }
    }
}

793 794
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
795
MAVLinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t first_message, QByteArray data, bool /*acked*/)
Gus Grubba's avatar
Gus Grubba committed
796
{
797 798 799 800
    //-- Disable timer if we got a message before an ACK for the start command
    if(_logRunning) {
        _ackTimer.stop();
    }
Gus Grubba's avatar
Gus Grubba committed
801 802
    if(_logProcessor && _logProcessor->valid()) {
        if(!_logProcessor->processStreamData(sequence, first_message, data)) {
Gus Grubba's avatar
Gus Grubba committed
803
            qCCritical(MAVLinkLogManagerLog) << "Error writing MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
804 805
            delete _logProcessor;
            _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
806 807 808 809 810
            _logRunning = false;
            _vehicle->stopMavlinkLog();
            emit logRunningChanged();
        }
    } else {
Gus Grubba's avatar
Gus Grubba committed
811
        qCWarning(MAVLinkLogManagerLog) << "MAVLink log data received when not expected.";
Gus Grubba's avatar
Gus Grubba committed
812 813 814
    }
}

815 816
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
817
MAVLinkLogManager::_commandLongAck(uint8_t /*compID*/, uint16_t command, uint8_t result)
818 819 820 821 822 823 824
{
    if(command == MAV_CMD_LOGGING_START || command == MAV_CMD_LOGGING_STOP) {
        _ackTimer.stop();
        //-- Did it fail?
        if(result) {
            if(command == MAV_CMD_LOGGING_STOP) {
                //-- Not that it could happen but...
Gus Grubba's avatar
Gus Grubba committed
825
                qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command failed.";
826 827
            } else {
                //-- Could not start logging for some reason.
Gus Grubba's avatar
Gus Grubba committed
828
                qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command failed.";
829 830 831 832 833 834 835 836
                _discardLog();
            }
        }
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
837
MAVLinkLogManager::_discardLog()
838 839
{
    //-- Delete (empty) log file (and record)
Gus Grubba's avatar
Gus Grubba committed
840 841 842 843
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _deleteLog(_logProcessor->record());
844
        }
Gus Grubba's avatar
Gus Grubba committed
845 846
        delete _logProcessor;
        _logProcessor = NULL;
847 848 849 850 851
    }
    _logRunning = false;
    emit logRunningChanged();
}

Gus Grubba's avatar
Gus Grubba committed
852 853
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
854
MAVLinkLogManager::_createNewLog()
855
{
Gus Grubba's avatar
Gus Grubba committed
856 857 858
    if(_logProcessor) {
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
859
    }
Gus Grubba's avatar
Gus Grubba committed
860
    _logProcessor = new MAVLinkLogProcessor;
Gus Grubba's avatar
Gus Grubba committed
861 862
    if(_logProcessor->create(this, _logPath, _vehicle->id())) {
        _insertNewLog(_logProcessor->record());
863 864
        emit logFilesChanged();
    } else {
Gus Grubba's avatar
Gus Grubba committed
865
        qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
866 867
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
868
    }
Gus Grubba's avatar
Gus Grubba committed
869
    return _logProcessor != NULL;
870 871 872 873
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
874
MAVLinkLogManager::_armedChanged(bool armed)
875 876 877 878
{
    if(_vehicle) {
        if(armed) {
            if(_enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
879
                startLogging();
880 881 882
            }
        } else {
            if(_logRunning && _enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
883
                stopLogging();
884 885 886 887
            }
        }
    }
}
Gus Grubba's avatar
Gus Grubba committed
888 889 890

//-----------------------------------------------------------------------------
QString
Gus Grubba's avatar
Gus Grubba committed
891
MAVLinkLogManager::_makeFilename(const QString& baseName)
Gus Grubba's avatar
Gus Grubba committed
892 893 894 895 896 897 898
{
    QString filePath = _logPath;
    filePath += "/";
    filePath += baseName;
    filePath += kUlogExtension;
    return filePath;
}