MAVLinkLogManager.cc 31.9 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
#include "QGCApplication.h"
12 13
#include "SettingsManager.h"

Gus Grubba's avatar
Gus Grubba committed
14 15 16 17 18 19 20 21 22 23
#include <QQmlContext>
#include <QQmlProperty>
#include <QQmlEngine>
#include <QtQml>
#include <QSettings>
#include <QHttpPart>
#include <QNetworkReply>
#include <QFile>
#include <QFileInfo>

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

26 27 28
static const char* kMAVLinkLogGroup         = "MAVLinkLogGroup";
static const char* kEmailAddressKey         = "Email";
static const char* kDescriptionsKey         = "Description";
29
static const char* kDefaultDescr            = "QGroundControl Session";
30
static const char* kPx4URLKey               = "LogURL";
31
static const char* kDefaultPx4URL           = "https://logs.px4.io/upload";
32 33 34
static const char* kEnableAutoUploadKey     = "EnableAutoUpload";
static const char* kEnableAutoStartKey      = "EnableAutoStart";
static const char* kEnableDeletetKey        = "EnableDelete";
Gus Grubba's avatar
Gus Grubba committed
35
static const char* kSidecarExtension        = ".uploaded";
36 37 38 39 40 41
static const char* kVideoURLKey             = "VideoURL";
static const char* kWindSpeedKey            = "WindSpeed";
static const char* kRateKey                 = "RateKey";
static const char* kPublicLogKey            = "PublicLog";
static const char* kFeedback                = "feedback";
static const char* kVideoURL                = "videoUrl";
Gus Grubba's avatar
Gus Grubba committed
42 43

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

64 65
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
66
MAVLinkLogFiles::setSize(quint32 size)
67 68 69 70 71
{
    _size = size;
    emit sizeChanged();
}

Gus Grubba's avatar
Gus Grubba committed
72 73
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
74
MAVLinkLogFiles::setSelected(bool selected)
Gus Grubba's avatar
Gus Grubba committed
75 76 77 78 79 80 81 82
{
    _selected = selected;
    emit selectedChanged();
    emit _manager->selectedCountChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
83
MAVLinkLogFiles::setUploading(bool uploading)
Gus Grubba's avatar
Gus Grubba committed
84 85 86 87 88 89 90
{
    _uploading = uploading;
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
91
MAVLinkLogFiles::setProgress(qreal progress)
Gus Grubba's avatar
Gus Grubba committed
92 93 94 95 96
{
    _progress = progress;
    emit progressChanged();
}

97 98
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
99
MAVLinkLogFiles::setWriting(bool writing)
100 101 102 103 104
{
    _writing = writing;
    emit writingChanged();
}

Gus Grubba's avatar
Gus Grubba committed
105 106
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
107
MAVLinkLogFiles::setUploaded(bool uploaded)
Gus Grubba's avatar
Gus Grubba committed
108 109 110 111 112
{
    _uploaded = uploaded;
    emit uploadedChanged();
}

Gus Grubba's avatar
Gus Grubba committed
113 114
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
115
MAVLinkLogProcessor::MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
116 117 118 119 120 121 122
    : _fd(NULL)
    , _written(0)
    , _sequence(-1)
    , _numDrops(0)
    , _gotHeader(false)
    , _error(false)
    , _record(NULL)
Gus Grubba's avatar
Gus Grubba committed
123
{
Gus Grubba's avatar
Gus Grubba committed
124 125 126
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
127
MAVLinkLogProcessor::~MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
128 129 130 131 132 133
{
    close();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
134
MAVLinkLogProcessor::close()
Gus Grubba's avatar
Gus Grubba committed
135 136 137 138
{
    if(_fd) {
        fclose(_fd);
        _fd = NULL;
Gus Grubba's avatar
Gus Grubba committed
139 140 141
    }
}

Gus Grubba's avatar
Gus Grubba committed
142 143
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
144
MAVLinkLogProcessor::valid()
Gus Grubba's avatar
Gus Grubba committed
145 146 147 148 149 150
{
    return (_fd != NULL) && (_record != NULL);
}

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

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
170
MAVLinkLogProcessor::_checkSequence(uint16_t seq, int& num_drops)
Gus Grubba's avatar
Gus Grubba committed
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 198 199 200 201 202 203
{
    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
204
MAVLinkLogProcessor::_writeData(void* data, int len)
Gus Grubba's avatar
Gus Grubba committed
205 206 207 208 209 210 211 212 213
{
    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
214
            qCDebug(MAVLinkLogManagerLog) << "File IO error:" << len << "bytes into" << _fileName;
Gus Grubba's avatar
Gus Grubba committed
215 216 217 218 219 220
        }
    }
}

//-----------------------------------------------------------------------------
QByteArray
Gus Grubba's avatar
Gus Grubba committed
221
MAVLinkLogProcessor::_writeUlogMessage(QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
{
    //-- 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
238
MAVLinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
Gus Grubba's avatar
Gus Grubba committed
239 240 241 242 243 244 245 246
{
    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.
247
                qCWarning(MAVLinkLogManagerLog) << "Corrupt log header. Canceling log download.";
Gus Grubba's avatar
Gus Grubba committed
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
                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();
Patrick José Pereira's avatar
Patrick José Pereira committed
268
            //-- If no useful information in this message. Drop it.
Gus Grubba's avatar
Gus Grubba committed
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
            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
297
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
298
//-----------------------------------------------------------------------------
299 300
MAVLinkLogManager::MAVLinkLogManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
301
    , _enableAutoUpload(true)
302
    , _enableAutoStart(false)
Gus Grubba's avatar
Gus Grubba committed
303 304
    , _nam(NULL)
    , _currentLogfile(NULL)
305 306
    , _vehicle(NULL)
    , _logRunning(false)
Gus Grubba's avatar
Gus Grubba committed
307
    , _loggingDisabled(false)
Gus Grubba's avatar
Gus Grubba committed
308
    , _logProcessor(NULL)
309
    , _deleteAfterUpload(false)
310 311
    , _windSpeed(-1)
    , _publicLog(false)
312
    , _logginDenied(false)
Gus Grubba's avatar
Gus Grubba committed
313 314 315
{
    //-- Get saved settings
    QSettings settings;
316
    settings.beginGroup(kMAVLinkLogGroup);
Gus Grubba's avatar
Gus Grubba committed
317 318 319
    setEmailAddress(settings.value(kEmailAddressKey, QString()).toString());
    setDescription(settings.value(kDescriptionsKey, QString(kDefaultDescr)).toString());
    setUploadURL(settings.value(kPx4URLKey, QString(kDefaultPx4URL)).toString());
320
    setVideoURL(settings.value(kVideoURLKey, QString()).toString());
321
    setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
322
    setEnableAutoStart(settings.value(kEnableAutoStartKey, false).toBool());
323
    setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
324 325 326
    setWindSpeed(settings.value(kWindSpeedKey, -1).toInt());
    setRating(settings.value(kRateKey, "notset").toString());
    setPublicLog(settings.value(kPublicLogKey, true).toBool());
Gus Grubba's avatar
Gus Grubba committed
327 328 329
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
330
MAVLinkLogManager::~MAVLinkLogManager()
Gus Grubba's avatar
Gus Grubba committed
331 332 333 334 335 336
{
    _logFiles.clear();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
337
MAVLinkLogManager::setToolbox(QGCToolbox* toolbox)
Gus Grubba's avatar
Gus Grubba committed
338
{
Gus Grubba's avatar
Gus Grubba committed
339 340
    QGCTool::setToolbox(toolbox);
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
Gus Grubba's avatar
Gus Grubba committed
341
    qmlRegisterUncreatableType<MAVLinkLogManager>("QGroundControl.MAVLinkLogManager", 1, 0, "MAVLinkLogManager", "Reference only");
342 343 344 345 346 347 348 349 350 351
    //-- Logging location
    _ulogExtension  = ".";
    _ulogExtension += qgcApp()->toolbox()->settingsManager()->appSettings()->logFileExtension;
    _logPath = qgcApp()->toolbox()->settingsManager()->appSettings()->logSavePath();
    if(!QDir(_logPath).exists()) {
        if(!QDir().mkpath(_logPath)) {
            qCWarning(MAVLinkLogManagerLog) << "Could not create MAVLink log download path:" << _logPath;
            _loggingDisabled = true;
        }
    }
Gus Grubba's avatar
Gus Grubba committed
352
    if(!_loggingDisabled) {
353 354 355 356 357 358 359 360
        //-- Load current list of logs
        QString filter = "*";
        filter += _ulogExtension;
        QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
        while(it.hasNext()) {
            _insertNewLog(new MAVLinkLogFiles(this, it.next()));
        }
        qCDebug(MAVLinkLogManagerLog) << "MAVLink logs directory:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
361
        connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkLogManager::_activeVehicleChanged);
Gus Grubba's avatar
Gus Grubba committed
362
    }
Gus Grubba's avatar
Gus Grubba committed
363 364 365 366
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
367
MAVLinkLogManager::setEmailAddress(QString email)
Gus Grubba's avatar
Gus Grubba committed
368 369 370
{
    _emailAddress = email;
    QSettings settings;
371
    settings.beginGroup(kMAVLinkLogGroup);
Gus Grubba's avatar
Gus Grubba committed
372 373 374 375 376 377
    settings.setValue(kEmailAddressKey, email);
    emit emailAddressChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
378
MAVLinkLogManager::setDescription(QString description)
Gus Grubba's avatar
Gus Grubba committed
379 380 381
{
    _description = description;
    QSettings settings;
382
    settings.beginGroup(kMAVLinkLogGroup);
Gus Grubba's avatar
Gus Grubba committed
383 384 385 386 387 388
    settings.setValue(kDescriptionsKey, description);
    emit descriptionChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
389
MAVLinkLogManager::setUploadURL(QString url)
Gus Grubba's avatar
Gus Grubba committed
390 391 392 393 394 395
{
    _uploadURL = url;
    if(_uploadURL.isEmpty()) {
        _uploadURL = kDefaultPx4URL;
    }
    QSettings settings;
396
    settings.beginGroup(kMAVLinkLogGroup);
Gus Grubba's avatar
Gus Grubba committed
397 398 399 400
    settings.setValue(kPx4URLKey, _uploadURL);
    emit uploadURLChanged();
}

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
//-----------------------------------------------------------------------------
void
MAVLinkLogManager::setFeedback(QString fb)
{
    _feedback = fb;
    emit feedbackChanged();
}

//-----------------------------------------------------------------------------
void
MAVLinkLogManager::setVideoURL(QString url)
{
    _videoURL = url;
    QSettings settings;
    settings.beginGroup(kMAVLinkLogGroup);
    settings.setValue(kVideoURLKey, url);
    emit videoURLChanged();
}

Gus Grubba's avatar
Gus Grubba committed
420 421
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
422
MAVLinkLogManager::setEnableAutoUpload(bool enable)
423 424 425
{
    _enableAutoUpload = enable;
    QSettings settings;
426
    settings.beginGroup(kMAVLinkLogGroup);
427 428 429 430 431 432
    settings.setValue(kEnableAutoUploadKey, enable);
    emit enableAutoUploadChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
433
MAVLinkLogManager::setEnableAutoStart(bool enable)
Gus Grubba's avatar
Gus Grubba committed
434
{
435
    _enableAutoStart = enable;
Gus Grubba's avatar
Gus Grubba committed
436
    QSettings settings;
437
    settings.beginGroup(kMAVLinkLogGroup);
438 439
    settings.setValue(kEnableAutoStartKey, enable);
    emit enableAutoStartChanged();
Gus Grubba's avatar
Gus Grubba committed
440 441
}

442 443
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
444
MAVLinkLogManager::setDeleteAfterUpload(bool enable)
445 446 447
{
    _deleteAfterUpload = enable;
    QSettings settings;
448
    settings.beginGroup(kMAVLinkLogGroup);
449 450 451 452
    settings.setValue(kEnableDeletetKey, enable);
    emit deleteAfterUploadChanged();
}

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
//-----------------------------------------------------------------------------
void
MAVLinkLogManager::setWindSpeed(int speed)
{
    _windSpeed = speed;
    QSettings settings;
    settings.beginGroup(kMAVLinkLogGroup);
    settings.setValue(kWindSpeedKey, speed);
    emit windSpeedChanged();
}

//-----------------------------------------------------------------------------
void
MAVLinkLogManager::setRating(QString rate)
{
    _rating = rate;
    QSettings settings;
    settings.beginGroup(kMAVLinkLogGroup);
    settings.setValue(kRateKey, rate);
    emit ratingChanged();
}

//-----------------------------------------------------------------------------
void
MAVLinkLogManager::setPublicLog(bool pub)
{
    _publicLog = pub;
    QSettings settings;
    settings.beginGroup(kMAVLinkLogGroup);
    settings.setValue(kPublicLogKey, pub);
    emit publicLogChanged();
}

Gus Grubba's avatar
Gus Grubba committed
486 487
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
488
MAVLinkLogManager::uploading()
Gus Grubba's avatar
Gus Grubba committed
489 490 491 492 493 494
{
    return _currentLogfile != NULL;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
495
MAVLinkLogManager::uploadLog()
Gus Grubba's avatar
Gus Grubba committed
496 497 498 499
{
    if(_currentLogfile) {
        _currentLogfile->setUploading(false);
    }
Gus Grubba's avatar
Gus Grubba committed
500
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
501
        _currentLogfile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
DonLakeFlyer's avatar
DonLakeFlyer committed
502 503 504 505 506 507 508 509 510 511 512
        if (_currentLogfile) {
            if(_currentLogfile->selected()) {
                _currentLogfile->setSelected(false);
                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
513
            }
DonLakeFlyer's avatar
DonLakeFlyer committed
514 515
        } else {
            qWarning() << "Internal error";
Gus Grubba's avatar
Gus Grubba committed
516 517 518
        }
    }
    _currentLogfile = NULL;
519 520 521 522 523
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
524
MAVLinkLogManager::_insertNewLog(MAVLinkLogFiles* newLog)
525 526 527 528 529 530 531
{
    //-- 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
532
            MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
533 534 535 536 537 538 539
            if(newLog->name() < f->name()) {
                _logFiles.insert(i, newLog);
                return;
            }
        }
        _logFiles.append(newLog);
    }
Gus Grubba's avatar
Gus Grubba committed
540 541
}

Gus Grubba's avatar
Gus Grubba committed
542 543
//-----------------------------------------------------------------------------
int
Gus Grubba's avatar
Gus Grubba committed
544
MAVLinkLogManager::_getFirstSelected()
Gus Grubba's avatar
Gus Grubba committed
545 546
{
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
547
        MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
DonLakeFlyer's avatar
DonLakeFlyer committed
548 549 550 551 552 553
        if (f) {
            if(f->selected()) {
                return i;
            }
        } else {
            qWarning() << "Internal error";
Gus Grubba's avatar
Gus Grubba committed
554 555 556 557 558
        }
    }
    return -1;
}

Gus Grubba's avatar
Gus Grubba committed
559 560
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
561
MAVLinkLogManager::deleteLog()
Gus Grubba's avatar
Gus Grubba committed
562
{
Gus Grubba's avatar
Gus Grubba committed
563 564 565 566 567
    while (true) {
        int idx = _getFirstSelected();
        if(idx < 0) {
            break;
        }
Gus Grubba's avatar
Gus Grubba committed
568
        MAVLinkLogFiles* log = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(idx));
569
        _deleteLog(log);
Gus Grubba's avatar
Gus Grubba committed
570
    }
Gus Grubba's avatar
Gus Grubba committed
571 572
}

573 574
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
575
MAVLinkLogManager::_deleteLog(MAVLinkLogFiles* log)
576
{
Gus Grubba's avatar
Gus Grubba committed
577
    QString filePath = _makeFilename(log->name());
578 579
    QFile gone(filePath);
    if(!gone.remove()) {
Gus Grubba's avatar
Gus Grubba committed
580
        qCWarning(MAVLinkLogManagerLog) << "Could not delete MAVLink log file:" << _logPath;
581
    }
Gus Grubba's avatar
Gus Grubba committed
582
    //-- Remove sidecar file (if any)
583
    filePath.replace(_ulogExtension, kSidecarExtension);
Gus Grubba's avatar
Gus Grubba committed
584 585 586 587 588
    QFile sgone(filePath);
    if(sgone.exists()) {
        sgone.remove();
    }
    //-- Remove file from list and delete record
589 590 591 592 593
    _logFiles.removeOne(log);
    delete log;
    emit logFilesChanged();
}

Gus Grubba's avatar
Gus Grubba committed
594 595
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
596
MAVLinkLogManager::cancelUpload()
Gus Grubba's avatar
Gus Grubba committed
597
{
Gus Grubba's avatar
Gus Grubba committed
598
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
599
        MAVLinkLogFiles* pLogFile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
DonLakeFlyer's avatar
DonLakeFlyer committed
600 601 602 603 604 605
        if (pLogFile) {
            if(pLogFile->selected() && pLogFile != _currentLogfile) {
                pLogFile->setSelected(false);
            }
        } else {
            qWarning() << "Internal error";
Gus Grubba's avatar
Gus Grubba committed
606 607 608 609 610 611 612
        }
    }
    if(_currentLogfile) {
        emit abortUpload();
    }
}

613 614
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
615
MAVLinkLogManager::startLogging()
616
{
617
    if(_vehicle && _vehicle->px4Firmware() && !_logginDenied) {
Gus Grubba's avatar
Gus Grubba committed
618 619 620 621 622
        if(_createNewLog()) {
            _vehicle->startMavlinkLog();
            _logRunning = true;
            emit logRunningChanged();
        }
623 624 625 626 627
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
628
MAVLinkLogManager::stopLogging()
629
{
630
    if(_vehicle && _vehicle->px4Firmware()) {
631
        //-- Tell vehicle to stop sending logs
632
        _vehicle->stopMavlinkLog();
633
    }
Gus Grubba's avatar
Gus Grubba committed
634 635 636 637
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _logProcessor->record()->setWriting(false);
638 639
            if(_enableAutoUpload) {
                //-- Queue log for auto upload (set selected flag)
Gus Grubba's avatar
Gus Grubba committed
640
                _logProcessor->record()->setSelected(true);
641 642
                if(!uploading()) {
                    uploadLog();
643
                }
Gus Grubba's avatar
Gus Grubba committed
644 645
            }
        }
Gus Grubba's avatar
Gus Grubba committed
646 647
        delete _logProcessor;
        _logProcessor = NULL;
648 649
        _logRunning = false;
        emit logRunningChanged();
650 651 652
    }
}

Gus Grubba's avatar
Gus Grubba committed
653 654 655 656 657 658 659 660 661 662 663 664
//-----------------------------------------------------------------------------
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
665
MAVLinkLogManager::_sendLog(const QString& logFile)
Gus Grubba's avatar
Gus Grubba committed
666 667 668
{
    QString defaultDescription = _description;
    if(_description.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
669
        qCWarning(MAVLinkLogManagerLog) << "Log description missing. Using defaults.";
Gus Grubba's avatar
Gus Grubba committed
670 671 672
        defaultDescription = kDefaultDescr;
    }
    if(_emailAddress.isEmpty()) {
673
        qCWarning(MAVLinkLogManagerLog) << "User email missing.";
Gus Grubba's avatar
Gus Grubba committed
674 675 676
        return false;
    }
    if(_uploadURL.isEmpty()) {
677
        qCWarning(MAVLinkLogManagerLog) << "Upload URL missing.";
Gus Grubba's avatar
Gus Grubba committed
678 679 680 681
        return false;
    }
    QFileInfo fi(logFile);
    if(!fi.exists()) {
682
        qCWarning(MAVLinkLogManagerLog) << "Log file missing:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
683 684
        return false;
    }
Gus Grubba's avatar
Gus Grubba committed
685
    QFile* file = new QFile(logFile);
Gus Grubba's avatar
Gus Grubba committed
686
    if(!file || !file->open(QIODevice::ReadOnly)) {
Gus Grubba's avatar
Gus Grubba committed
687 688 689
        if(file) {
            delete file;
        }
690
        qCWarning(MAVLinkLogManagerLog) << "Could not open log file:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
691 692 693
        return false;
    }
    if(!_nam) {
Gus Grubba's avatar
Gus Grubba committed
694
        _nam = new QNetworkAccessManager(this);
Gus Grubba's avatar
Gus Grubba committed
695 696 697 698 699 700
    }
    QNetworkProxy savedProxy = _nam->proxy();
    QNetworkProxy tempProxy;
    tempProxy.setType(QNetworkProxy::DefaultProxy);
    _nam->setProxy(tempProxy);
    //-- Build POST request
Gus Grubba's avatar
Gus Grubba committed
701
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
Gus Grubba's avatar
Gus Grubba committed
702 703
    QHttpPart emailPart = create_form_part("email", _emailAddress);
    QHttpPart descriptionPart = create_form_part("description", _description);
704 705 706 707 708 709
    QHttpPart sourcePart      = create_form_part("source", "QGroundControl");
    QHttpPart versionPart     = create_form_part("version", _app->applicationVersion());
    QHttpPart typePart        = create_form_part("type", "flightreport");
    QHttpPart windPart        = create_form_part("windSpeed", QString::number(_windSpeed));
    QHttpPart ratingPart      = create_form_part("rating", _rating);
    QHttpPart publicPart      = create_form_part("public", _publicLog ? "true" : "false");
Gus Grubba's avatar
Gus Grubba committed
710 711 712 713 714
    //-- Assemble request and POST it
    multiPart->append(emailPart);
    multiPart->append(descriptionPart);
    multiPart->append(sourcePart);
    multiPart->append(versionPart);
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
    multiPart->append(typePart);
    multiPart->append(windPart);
    multiPart->append(ratingPart);
    multiPart->append(publicPart);
    //-- Optional
    QHttpPart feedbackPart;
    if(_feedback.isEmpty()) {
        feedbackPart = create_form_part(kFeedback, "None Given");
    } else {
        feedbackPart = create_form_part(kFeedback, _feedback);
    }
    multiPart->append(feedbackPart);
    QHttpPart videoPart;
    if(_videoURL.isEmpty()) {
        videoPart = create_form_part(kVideoURL, "None");
    } else {
        videoPart = create_form_part(kVideoURL, _videoURL);
    }
    multiPart->append(videoPart);
    //-- Actual Log File
    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);
Gus Grubba's avatar
Gus Grubba committed
739 740 741
    multiPart->append(logPart);
    file->setParent(multiPart);
    QNetworkRequest request(_uploadURL);
742
#if QT_VERSION > 0x050600
Gus Grubba's avatar
Gus Grubba committed
743
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
744
#endif
Gus Grubba's avatar
Gus Grubba committed
745
    QNetworkReply* reply = _nam->post(request, multiPart);
Gus Grubba's avatar
Gus Grubba committed
746 747 748 749
    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
750
    multiPart->setParent(reply);
Gus Grubba's avatar
Gus Grubba committed
751
    qCDebug(MAVLinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
Gus Grubba's avatar
Gus Grubba committed
752 753 754 755 756 757
    _nam->setProxy(savedProxy);
    return true;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
758
MAVLinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
759
{
Gus Grubba's avatar
Gus Grubba committed
760
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
761 762 763 764 765 766
    emit readyRead(data);
    return http_code == 200;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
767
MAVLinkLogManager::_dataAvailable()
Gus Grubba's avatar
Gus Grubba committed
768
{
Gus Grubba's avatar
Gus Grubba committed
769
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
770 771 772 773
    if(!reply) {
        return;
    }
    QByteArray data = reply->readAll();
Gus Grubba's avatar
Gus Grubba committed
774
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
775 776 777 778
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
779
MAVLinkLogManager::_uploadFinished()
Gus Grubba's avatar
Gus Grubba committed
780
{
Gus Grubba's avatar
Gus Grubba committed
781
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
782 783 784 785 786 787
    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
788
        qCDebug(MAVLinkLogManagerLog) << "Log uploaded.";
Gus Grubba's avatar
Gus Grubba committed
789
        emit succeed();
790 791 792 793 794
        if(_deleteAfterUpload) {
            if(_currentLogfile) {
                _deleteLog(_currentLogfile);
                _currentLogfile = NULL;
            }
Gus Grubba's avatar
Gus Grubba committed
795 796 797 798 799
        } else {
            if(_currentLogfile) {
                _currentLogfile->setUploaded(true);
                //-- Write side-car file to flag it as uploaded
                QString sideCar = _makeFilename(_currentLogfile->name());
800
                sideCar.replace(_ulogExtension, kSidecarExtension);
Gus Grubba's avatar
Gus Grubba committed
801 802 803 804 805
                FILE* f = fopen(sideCar.toLatin1().data(), "wb");
                if(f) {
                    fclose(f);
                }
            }
806
        }
Gus Grubba's avatar
Gus Grubba committed
807
    } else {
Gus Grubba's avatar
Gus Grubba committed
808
        qCWarning(MAVLinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
Gus Grubba's avatar
Gus Grubba committed
809 810 811 812 813 814 815 816 817
        emit failed();
    }
    reply->deleteLater();
    //-- Next (if any)
    uploadLog();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
818
MAVLinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
Gus Grubba's avatar
Gus Grubba committed
819 820 821
{
    if(bytesTotal) {
        qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
822
        if(_currentLogfile) {
Gus Grubba's avatar
Gus Grubba committed
823
            _currentLogfile->setProgress(progress);
Gus Grubba's avatar
Gus Grubba committed
824
        }
Gus Grubba's avatar
Gus Grubba committed
825
    }
Gus Grubba's avatar
Gus Grubba committed
826
    qCDebug(MAVLinkLogManagerLog) << bytesSent << "of" << bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
827
}
828 829 830

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
831
MAVLinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
832 833 834 835 836
{
    //-- 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
837
    //   For now, we only handle one log download at a time.
838
    // Disconnect the previous one (if any)
839
    if(_vehicle && _vehicle->px4Firmware()) {
840 841 842
        disconnect(_vehicle, &Vehicle::armedChanged,        this, &MAVLinkLogManager::_armedChanged);
        disconnect(_vehicle, &Vehicle::mavlinkLogData,      this, &MAVLinkLogManager::_mavlinkLogData);
        disconnect(_vehicle, &Vehicle::mavCommandResult,    this, &MAVLinkLogManager::_mavCommandResult);
843
        _vehicle = NULL;
844 845
        //-- Stop logging (if that's the case)
        stopLogging();
846 847 848
        emit canStartLogChanged();
    }
    // Connect new system
849
    if(vehicle && vehicle->px4Firmware()) {
850
        _vehicle = vehicle;
851 852
        //-- Reset logging denied flag as well
        _logginDenied = false;
853 854 855
        connect(_vehicle, &Vehicle::armedChanged,       this, &MAVLinkLogManager::_armedChanged);
        connect(_vehicle, &Vehicle::mavlinkLogData,     this, &MAVLinkLogManager::_mavlinkLogData);
        connect(_vehicle, &Vehicle::mavCommandResult,   this, &MAVLinkLogManager::_mavCommandResult);
856 857 858 859 860 861
        emit canStartLogChanged();
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
862
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
863
{
Gus Grubba's avatar
Gus Grubba committed
864 865
    if(_logProcessor && _logProcessor->valid()) {
        if(!_logProcessor->processStreamData(sequence, first_message, data)) {
866
            qCWarning(MAVLinkLogManagerLog) << "Error writing MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
867 868
            delete _logProcessor;
            _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
869 870 871 872 873
            _logRunning = false;
            _vehicle->stopMavlinkLog();
            emit logRunningChanged();
        }
    } else {
Gus Grubba's avatar
Gus Grubba committed
874
        qCWarning(MAVLinkLogManagerLog) << "MAVLink log data received when not expected.";
Gus Grubba's avatar
Gus Grubba committed
875 876 877
    }
}

878 879
//-----------------------------------------------------------------------------
void
880
MAVLinkLogManager::_mavCommandResult(int vehicleId, int component, int command, int result, bool noReponseFromVehicle)
881
{
882 883 884 885
    Q_UNUSED(vehicleId);
    Q_UNUSED(component);
    Q_UNUSED(noReponseFromVehicle)

886 887
    if(command == MAV_CMD_LOGGING_START || command == MAV_CMD_LOGGING_STOP) {
        //-- Did it fail?
888
        if(result != MAV_RESULT_ACCEPTED) {
889 890
            if(command == MAV_CMD_LOGGING_STOP) {
                //-- Not that it could happen but...
Gus Grubba's avatar
Gus Grubba committed
891
                qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command failed.";
892 893
            } else {
                //-- Could not start logging for some reason.
894 895 896 897 898 899
                if(result == MAV_RESULT_DENIED) {
                    _logginDenied = true;
                    qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command denied.";
                } else {
                    qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command failed.";
                }
900 901 902 903 904 905 906 907
                _discardLog();
            }
        }
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
908
MAVLinkLogManager::_discardLog()
909 910
{
    //-- Delete (empty) log file (and record)
Gus Grubba's avatar
Gus Grubba committed
911 912 913 914
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _deleteLog(_logProcessor->record());
915
        }
Gus Grubba's avatar
Gus Grubba committed
916 917
        delete _logProcessor;
        _logProcessor = NULL;
918 919 920 921 922
    }
    _logRunning = false;
    emit logRunningChanged();
}

Gus Grubba's avatar
Gus Grubba committed
923 924
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
925
MAVLinkLogManager::_createNewLog()
926
{
Gus Grubba's avatar
Gus Grubba committed
927 928 929
    if(_logProcessor) {
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
930
    }
Gus Grubba's avatar
Gus Grubba committed
931
    _logProcessor = new MAVLinkLogProcessor;
Gus Grubba's avatar
Gus Grubba committed
932 933
    if(_logProcessor->create(this, _logPath, _vehicle->id())) {
        _insertNewLog(_logProcessor->record());
934 935
        emit logFilesChanged();
    } else {
936
        qCWarning(MAVLinkLogManagerLog) << "Could not create MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
937 938
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
939
    }
Gus Grubba's avatar
Gus Grubba committed
940
    return _logProcessor != NULL;
941 942 943 944
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
945
MAVLinkLogManager::_armedChanged(bool armed)
946
{
947
    if(_vehicle && _vehicle->px4Firmware()) {
948 949
        if(armed) {
            if(_enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
950
                startLogging();
951 952 953
            }
        } else {
            if(_logRunning && _enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
954
                stopLogging();
955 956 957 958
            }
        }
    }
}
Gus Grubba's avatar
Gus Grubba committed
959 960 961

//-----------------------------------------------------------------------------
QString
Gus Grubba's avatar
Gus Grubba committed
962
MAVLinkLogManager::_makeFilename(const QString& baseName)
Gus Grubba's avatar
Gus Grubba committed
963 964 965 966
{
    QString filePath = _logPath;
    filePath += "/";
    filePath += baseName;
967
    filePath += _ulogExtension;
Gus Grubba's avatar
Gus Grubba committed
968 969
    return filePath;
}