MAVLinkLogManager.cc 31 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>

Gus Grubba's avatar
Gus Grubba committed
22
QGC_LOGGING_CATEGORY(MAVLinkLogManagerLog, "MAVLinkLogManagerLog")
Gus Grubba's avatar
Gus Grubba committed
23

24 25 26
static const char* kMAVLinkLogGroup         = "MAVLinkLogGroup";
static const char* kEmailAddressKey         = "Email";
static const char* kDescriptionsKey         = "Description";
27
static const char* kDefaultDescr            = "QGroundControl Session";
28
static const char* kPx4URLKey               = "LogURL";
29
static const char* kDefaultPx4URL           = "http://logs.px4.io/upload";
30 31 32
static const char* kEnableAutoUploadKey     = "EnableAutoUpload";
static const char* kEnableAutoStartKey      = "EnableAutoStart";
static const char* kEnableDeletetKey        = "EnableDelete";
Gus Grubba's avatar
Gus Grubba committed
33 34
static const char* kUlogExtension           = ".ulg";
static const char* kSidecarExtension        = ".uploaded";
35 36 37 38 39 40
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
41 42

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
43
MAVLinkLogFiles::MAVLinkLogFiles(MAVLinkLogManager* manager, const QString& filePath, bool newFile)
Gus Grubba's avatar
Gus Grubba committed
44 45 46 47 48
    : _manager(manager)
    , _size(0)
    , _selected(false)
    , _uploading(false)
    , _progress(0)
Gus Grubba's avatar
Gus Grubba committed
49
    , _writing(false)
Gus Grubba's avatar
Gus Grubba committed
50
    , _uploaded(false)
Gus Grubba's avatar
Gus Grubba committed
51 52 53
{
    QFileInfo fi(filePath);
    _name = fi.baseName();
Gus Grubba's avatar
Gus Grubba committed
54 55 56 57 58 59 60
    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
61 62
}

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

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

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

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

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

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

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

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

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

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

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
150
MAVLinkLogProcessor::create(MAVLinkLogManager* manager, const QString path, uint8_t id)
Gus Grubba's avatar
Gus Grubba committed
151 152 153 154 155 156 157 158
{
    _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
159
        _record = new MAVLinkLogFiles(manager, _fileName, true);
Gus Grubba's avatar
Gus Grubba committed
160 161 162 163 164 165 166 167 168
        _record->setWriting(true);
        _sequence = -1;
        return true;
    }
    return false;
}

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

//-----------------------------------------------------------------------------
QByteArray
Gus Grubba's avatar
Gus Grubba committed
220
MAVLinkLogProcessor::_writeUlogMessage(QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
{
    //-- 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
237
MAVLinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
Gus Grubba's avatar
Gus Grubba committed
238 239 240 241 242 243 244 245
{
    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
246
                qCCritical(MAVLinkLogManagerLog) << "Corrupt log header. Canceling log download.";
Gus Grubba's avatar
Gus Grubba committed
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 291 292 293 294 295
                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
296
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
297
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
298
MAVLinkLogManager::MAVLinkLogManager(QGCApplication* app)
Gus Grubba's avatar
Gus Grubba committed
299
    : QGCTool(app)
300
    , _enableAutoUpload(true)
301
    , _enableAutoStart(false)
Gus Grubba's avatar
Gus Grubba committed
302 303
    , _nam(NULL)
    , _currentLogfile(NULL)
304 305
    , _vehicle(NULL)
    , _logRunning(false)
Gus Grubba's avatar
Gus Grubba committed
306
    , _loggingDisabled(false)
Gus Grubba's avatar
Gus Grubba committed
307
    , _logProcessor(NULL)
308
    , _deleteAfterUpload(false)
309 310
    , _windSpeed(-1)
    , _publicLog(false)
Gus Grubba's avatar
Gus Grubba committed
311 312 313
{
    //-- Get saved settings
    QSettings settings;
314
    settings.beginGroup(kMAVLinkLogGroup);
Gus Grubba's avatar
Gus Grubba committed
315 316 317
    setEmailAddress(settings.value(kEmailAddressKey, QString()).toString());
    setDescription(settings.value(kDescriptionsKey, QString(kDefaultDescr)).toString());
    setUploadURL(settings.value(kPx4URLKey, QString(kDefaultPx4URL)).toString());
318
    setVideoURL(settings.value(kVideoURLKey, QString()).toString());
319
    setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
320
    setEnableAutoStart(settings.value(kEnableAutoStartKey, false).toBool());
321
    setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
322 323 324
    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
325 326
    //-- Logging location
    _logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
Gus Grubba's avatar
Gus Grubba committed
327
    _logPath += "/MAVLinkLogs";
Gus Grubba's avatar
Gus Grubba committed
328
    if(!QDir(_logPath).exists()) {
Gus Grubba's avatar
Gus Grubba committed
329
        if(!QDir().mkpath(_logPath)) {
Gus Grubba's avatar
Gus Grubba committed
330
            qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log download path:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
331
            _loggingDisabled = true;
Gus Grubba's avatar
Gus Grubba committed
332 333
        }
    }
Gus Grubba's avatar
Gus Grubba committed
334 335
    if(!_loggingDisabled) {
        //-- Load current list of logs
Gus Grubba's avatar
Gus Grubba committed
336 337 338
        QString filter = "*";
        filter += kUlogExtension;
        QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
Gus Grubba's avatar
Gus Grubba committed
339
        while(it.hasNext()) {
Gus Grubba's avatar
Gus Grubba committed
340
            _insertNewLog(new MAVLinkLogFiles(this, it.next()));
Gus Grubba's avatar
Gus Grubba committed
341
        }
Gus Grubba's avatar
Gus Grubba committed
342
        qCDebug(MAVLinkLogManagerLog) << "MAVLink logs directory:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
343 344 345 346
    }
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
347
MAVLinkLogManager::~MAVLinkLogManager()
Gus Grubba's avatar
Gus Grubba committed
348 349 350 351 352 353
{
    _logFiles.clear();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
354
MAVLinkLogManager::setToolbox(QGCToolbox* toolbox)
Gus Grubba's avatar
Gus Grubba committed
355
{
Gus Grubba's avatar
Gus Grubba committed
356 357
    QGCTool::setToolbox(toolbox);
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
Gus Grubba's avatar
Gus Grubba committed
358
    qmlRegisterUncreatableType<MAVLinkLogManager>("QGroundControl.MAVLinkLogManager", 1, 0, "MAVLinkLogManager", "Reference only");
Gus Grubba's avatar
Gus Grubba committed
359
    if(!_loggingDisabled) {
Gus Grubba's avatar
Gus Grubba committed
360
        connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkLogManager::_activeVehicleChanged);
Gus Grubba's avatar
Gus Grubba committed
361
    }
Gus Grubba's avatar
Gus Grubba committed
362 363 364 365
}

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

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

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

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
//-----------------------------------------------------------------------------
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
419 420
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
421
MAVLinkLogManager::setEnableAutoUpload(bool enable)
422 423 424
{
    _enableAutoUpload = enable;
    QSettings settings;
425
    settings.beginGroup(kMAVLinkLogGroup);
426 427 428 429 430 431
    settings.setValue(kEnableAutoUploadKey, enable);
    emit enableAutoUploadChanged();
}

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

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

452 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
//-----------------------------------------------------------------------------
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
485 486
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
487
MAVLinkLogManager::uploading()
Gus Grubba's avatar
Gus Grubba committed
488 489 490 491 492 493
{
    return _currentLogfile != NULL;
}

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

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

Gus Grubba's avatar
Gus Grubba committed
538 539
//-----------------------------------------------------------------------------
int
Gus Grubba's avatar
Gus Grubba committed
540
MAVLinkLogManager::_getFirstSelected()
Gus Grubba's avatar
Gus Grubba committed
541 542
{
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
543
        MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
544 545 546 547 548 549 550 551
        Q_ASSERT(f);
        if(f->selected()) {
            return i;
        }
    }
    return -1;
}

Gus Grubba's avatar
Gus Grubba committed
552 553
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
554
MAVLinkLogManager::deleteLog()
Gus Grubba's avatar
Gus Grubba committed
555
{
Gus Grubba's avatar
Gus Grubba committed
556 557 558 559 560
    while (true) {
        int idx = _getFirstSelected();
        if(idx < 0) {
            break;
        }
Gus Grubba's avatar
Gus Grubba committed
561
        MAVLinkLogFiles* log = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(idx));
562
        _deleteLog(log);
Gus Grubba's avatar
Gus Grubba committed
563
    }
Gus Grubba's avatar
Gus Grubba committed
564 565
}

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

Gus Grubba's avatar
Gus Grubba committed
587 588
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
589
MAVLinkLogManager::cancelUpload()
Gus Grubba's avatar
Gus Grubba committed
590
{
Gus Grubba's avatar
Gus Grubba committed
591
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
592
        MAVLinkLogFiles* pLogFile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
593 594 595 596 597 598 599 600 601 602
        Q_ASSERT(pLogFile);
        if(pLogFile->selected() && pLogFile != _currentLogfile) {
            pLogFile->setSelected(false);
        }
    }
    if(_currentLogfile) {
        emit abortUpload();
    }
}

603 604
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
605
MAVLinkLogManager::startLogging()
606 607
{
    if(_vehicle) {
Gus Grubba's avatar
Gus Grubba committed
608 609 610 611 612
        if(_createNewLog()) {
            _vehicle->startMavlinkLog();
            _logRunning = true;
            emit logRunningChanged();
        }
613 614 615 616 617
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
618
MAVLinkLogManager::stopLogging()
619 620
{
    if(_vehicle) {
621
        //-- Tell vehicle to stop sending logs
622
        _vehicle->stopMavlinkLog();
623
    }
Gus Grubba's avatar
Gus Grubba committed
624 625 626 627
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _logProcessor->record()->setWriting(false);
628 629
            if(_enableAutoUpload) {
                //-- Queue log for auto upload (set selected flag)
Gus Grubba's avatar
Gus Grubba committed
630
                _logProcessor->record()->setSelected(true);
631 632
                if(!uploading()) {
                    uploadLog();
633
                }
Gus Grubba's avatar
Gus Grubba committed
634 635
            }
        }
Gus Grubba's avatar
Gus Grubba committed
636 637
        delete _logProcessor;
        _logProcessor = NULL;
638 639
        _logRunning = false;
        emit logRunningChanged();
640 641 642
    }
}

Gus Grubba's avatar
Gus Grubba committed
643 644 645 646 647 648 649 650 651 652 653 654
//-----------------------------------------------------------------------------
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
655
MAVLinkLogManager::_sendLog(const QString& logFile)
Gus Grubba's avatar
Gus Grubba committed
656 657 658
{
    QString defaultDescription = _description;
    if(_description.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
659
        qCWarning(MAVLinkLogManagerLog) << "Log description missing. Using defaults.";
Gus Grubba's avatar
Gus Grubba committed
660 661 662
        defaultDescription = kDefaultDescr;
    }
    if(_emailAddress.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
663
        qCCritical(MAVLinkLogManagerLog) << "User email missing.";
Gus Grubba's avatar
Gus Grubba committed
664 665 666
        return false;
    }
    if(_uploadURL.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
667
        qCCritical(MAVLinkLogManagerLog) << "Upload URL missing.";
Gus Grubba's avatar
Gus Grubba committed
668 669 670 671
        return false;
    }
    QFileInfo fi(logFile);
    if(!fi.exists()) {
Gus Grubba's avatar
Gus Grubba committed
672
        qCCritical(MAVLinkLogManagerLog) << "Log file missing:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
673 674
        return false;
    }
Gus Grubba's avatar
Gus Grubba committed
675
    QFile* file = new QFile(logFile);
Gus Grubba's avatar
Gus Grubba committed
676
    if(!file || !file->open(QIODevice::ReadOnly)) {
Gus Grubba's avatar
Gus Grubba committed
677 678 679
        if(file) {
            delete file;
        }
Gus Grubba's avatar
Gus Grubba committed
680
        qCCritical(MAVLinkLogManagerLog) << "Could not open log file:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
681 682 683
        return false;
    }
    if(!_nam) {
Gus Grubba's avatar
Gus Grubba committed
684
        _nam = new QNetworkAccessManager(this);
Gus Grubba's avatar
Gus Grubba committed
685 686 687 688 689 690
    }
    QNetworkProxy savedProxy = _nam->proxy();
    QNetworkProxy tempProxy;
    tempProxy.setType(QNetworkProxy::DefaultProxy);
    _nam->setProxy(tempProxy);
    //-- Build POST request
Gus Grubba's avatar
Gus Grubba committed
691
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
Gus Grubba's avatar
Gus Grubba committed
692 693
    QHttpPart emailPart = create_form_part("email", _emailAddress);
    QHttpPart descriptionPart = create_form_part("description", _description);
694 695 696 697 698 699
    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
700 701 702 703 704
    //-- Assemble request and POST it
    multiPart->append(emailPart);
    multiPart->append(descriptionPart);
    multiPart->append(sourcePart);
    multiPart->append(versionPart);
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
    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
729 730 731
    multiPart->append(logPart);
    file->setParent(multiPart);
    QNetworkRequest request(_uploadURL);
732
#if QT_VERSION > 0x050600
Gus Grubba's avatar
Gus Grubba committed
733
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
734
#endif
Gus Grubba's avatar
Gus Grubba committed
735
    QNetworkReply* reply = _nam->post(request, multiPart);
Gus Grubba's avatar
Gus Grubba committed
736 737 738 739
    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
740
    multiPart->setParent(reply);
Gus Grubba's avatar
Gus Grubba committed
741
    qCDebug(MAVLinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
Gus Grubba's avatar
Gus Grubba committed
742 743 744 745 746 747
    _nam->setProxy(savedProxy);
    return true;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
748
MAVLinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
749
{
Gus Grubba's avatar
Gus Grubba committed
750
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
751 752 753 754 755 756
    emit readyRead(data);
    return http_code == 200;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
757
MAVLinkLogManager::_dataAvailable()
Gus Grubba's avatar
Gus Grubba committed
758
{
Gus Grubba's avatar
Gus Grubba committed
759
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
760 761 762 763
    if(!reply) {
        return;
    }
    QByteArray data = reply->readAll();
Gus Grubba's avatar
Gus Grubba committed
764
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
765 766 767 768
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
769
MAVLinkLogManager::_uploadFinished()
Gus Grubba's avatar
Gus Grubba committed
770
{
Gus Grubba's avatar
Gus Grubba committed
771
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
772 773 774 775 776 777
    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
778
        qCDebug(MAVLinkLogManagerLog) << "Log uploaded.";
Gus Grubba's avatar
Gus Grubba committed
779
        emit succeed();
780 781 782 783 784
        if(_deleteAfterUpload) {
            if(_currentLogfile) {
                _deleteLog(_currentLogfile);
                _currentLogfile = NULL;
            }
Gus Grubba's avatar
Gus Grubba committed
785 786 787 788 789 790 791 792 793 794 795
        } 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);
                }
            }
796
        }
Gus Grubba's avatar
Gus Grubba committed
797
    } else {
Gus Grubba's avatar
Gus Grubba committed
798
        qCWarning(MAVLinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
Gus Grubba's avatar
Gus Grubba committed
799 800 801 802 803 804 805 806 807
        emit failed();
    }
    reply->deleteLater();
    //-- Next (if any)
    uploadLog();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
808
MAVLinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
Gus Grubba's avatar
Gus Grubba committed
809 810 811
{
    if(bytesTotal) {
        qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
812
        if(_currentLogfile) {
Gus Grubba's avatar
Gus Grubba committed
813
            _currentLogfile->setProgress(progress);
Gus Grubba's avatar
Gus Grubba committed
814
        }
Gus Grubba's avatar
Gus Grubba committed
815
    }
Gus Grubba's avatar
Gus Grubba committed
816
    qCDebug(MAVLinkLogManagerLog) << bytesSent << "of" << bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
817
}
818 819 820

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
821
MAVLinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
822 823 824 825 826
{
    //-- 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
827
    //   For now, we only handle one log download at a time.
828
    // Disconnect the previous one (if any)
Gus Grubba's avatar
Gus Grubba committed
829
    if(_vehicle) {
830 831 832
        disconnect(_vehicle, &Vehicle::armedChanged,        this, &MAVLinkLogManager::_armedChanged);
        disconnect(_vehicle, &Vehicle::mavlinkLogData,      this, &MAVLinkLogManager::_mavlinkLogData);
        disconnect(_vehicle, &Vehicle::mavCommandResult,    this, &MAVLinkLogManager::_mavCommandResult);
833
        _vehicle = NULL;
834 835
        //-- Stop logging (if that's the case)
        stopLogging();
836 837 838
        emit canStartLogChanged();
    }
    // Connect new system
Gus Grubba's avatar
Gus Grubba committed
839
    if(vehicle) {
840
        _vehicle = vehicle;
841 842 843
        connect(_vehicle, &Vehicle::armedChanged,       this, &MAVLinkLogManager::_armedChanged);
        connect(_vehicle, &Vehicle::mavlinkLogData,     this, &MAVLinkLogManager::_mavlinkLogData);
        connect(_vehicle, &Vehicle::mavCommandResult,   this, &MAVLinkLogManager::_mavCommandResult);
844 845 846 847 848 849
        emit canStartLogChanged();
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
850
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
851
{
Gus Grubba's avatar
Gus Grubba committed
852 853
    if(_logProcessor && _logProcessor->valid()) {
        if(!_logProcessor->processStreamData(sequence, first_message, data)) {
Gus Grubba's avatar
Gus Grubba committed
854
            qCCritical(MAVLinkLogManagerLog) << "Error writing MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
855 856
            delete _logProcessor;
            _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
857 858 859 860 861
            _logRunning = false;
            _vehicle->stopMavlinkLog();
            emit logRunningChanged();
        }
    } else {
Gus Grubba's avatar
Gus Grubba committed
862
        qCWarning(MAVLinkLogManagerLog) << "MAVLink log data received when not expected.";
Gus Grubba's avatar
Gus Grubba committed
863 864 865
    }
}

866 867
//-----------------------------------------------------------------------------
void
868
MAVLinkLogManager::_mavCommandResult(int vehicleId, int component, int command, int result, bool noReponseFromVehicle)
869
{
870 871 872 873
    Q_UNUSED(vehicleId);
    Q_UNUSED(component);
    Q_UNUSED(noReponseFromVehicle)

874 875
    if(command == MAV_CMD_LOGGING_START || command == MAV_CMD_LOGGING_STOP) {
        //-- Did it fail?
876
        if(result != MAV_RESULT_ACCEPTED) {
877 878
            if(command == MAV_CMD_LOGGING_STOP) {
                //-- Not that it could happen but...
Gus Grubba's avatar
Gus Grubba committed
879
                qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command failed.";
880 881
            } else {
                //-- Could not start logging for some reason.
Gus Grubba's avatar
Gus Grubba committed
882
                qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command failed.";
883 884 885 886 887 888 889 890
                _discardLog();
            }
        }
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
891
MAVLinkLogManager::_discardLog()
892 893
{
    //-- Delete (empty) log file (and record)
Gus Grubba's avatar
Gus Grubba committed
894 895 896 897
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _deleteLog(_logProcessor->record());
898
        }
Gus Grubba's avatar
Gus Grubba committed
899 900
        delete _logProcessor;
        _logProcessor = NULL;
901 902 903 904 905
    }
    _logRunning = false;
    emit logRunningChanged();
}

Gus Grubba's avatar
Gus Grubba committed
906 907
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
908
MAVLinkLogManager::_createNewLog()
909
{
Gus Grubba's avatar
Gus Grubba committed
910 911 912
    if(_logProcessor) {
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
913
    }
Gus Grubba's avatar
Gus Grubba committed
914
    _logProcessor = new MAVLinkLogProcessor;
Gus Grubba's avatar
Gus Grubba committed
915 916
    if(_logProcessor->create(this, _logPath, _vehicle->id())) {
        _insertNewLog(_logProcessor->record());
917 918
        emit logFilesChanged();
    } else {
Gus Grubba's avatar
Gus Grubba committed
919
        qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
920 921
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
922
    }
Gus Grubba's avatar
Gus Grubba committed
923
    return _logProcessor != NULL;
924 925 926 927
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
928
MAVLinkLogManager::_armedChanged(bool armed)
929 930 931 932
{
    if(_vehicle) {
        if(armed) {
            if(_enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
933
                startLogging();
934 935 936
            }
        } else {
            if(_logRunning && _enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
937
                stopLogging();
938 939 940 941
            }
        }
    }
}
Gus Grubba's avatar
Gus Grubba committed
942 943 944

//-----------------------------------------------------------------------------
QString
Gus Grubba's avatar
Gus Grubba committed
945
MAVLinkLogManager::_makeFilename(const QString& baseName)
Gus Grubba's avatar
Gus Grubba committed
946 947 948 949 950 951 952
{
    QString filePath = _logPath;
    filePath += "/";
    filePath += baseName;
    filePath += kUlogExtension;
    return filePath;
}