Commit 93ee9ec2 authored by Gus Grubba's avatar Gus Grubba Committed by GitHub

Merge pull request #4180 from dogmaphobic/MavlinkLogUploader

Mavlink log uploader
parents 5c94cda4 c77d8df1
......@@ -334,6 +334,7 @@ HEADERS += \
src/uas/UAS.h \
src/uas/UASInterface.h \
src/uas/UASMessageHandler.h \
src/Vehicle/MavlinkLogManager.h \
src/ui/toolbar/MainToolBarController.h \
src/AutoPilotPlugins/PX4/PX4AirframeLoader.h \
src/AutoPilotPlugins/APM/APMAirframeLoader.h \
......@@ -498,6 +499,7 @@ SOURCES += \
src/QmlControls/QmlObjectListModel.cc \
src/uas/UAS.cc \
src/uas/UASMessageHandler.cc \
src/Vehicle/MavlinkLogManager.cc \
src/ui/toolbar/MainToolBarController.cc \
src/AutoPilotPlugins/PX4/PX4AirframeLoader.cc \
src/AutoPilotPlugins/APM/APMAirframeLoader.cc \
......
......@@ -28,6 +28,7 @@
#include "FollowMe.h"
#include "PositionManager.h"
#include "VideoManager.h"
#include "MavlinkLogManager.h"
QGCToolbox::QGCToolbox(QGCApplication* app)
: _audioOutput(NULL)
......@@ -50,6 +51,7 @@ QGCToolbox::QGCToolbox(QGCApplication* app)
, _followMe(NULL)
, _qgcPositionManager(NULL)
, _videoManager(NULL)
, _mavlinkLogManager(NULL)
{
_audioOutput = new GAudioOutput(app);
_autopilotPluginManager = new AutoPilotPluginManager(app);
......@@ -71,6 +73,7 @@ QGCToolbox::QGCToolbox(QGCApplication* app)
_qgcPositionManager = new QGCPositionManager(app);
_followMe = new FollowMe(app);
_videoManager = new VideoManager(app);
_mavlinkLogManager = new MavlinkLogManager(app);
}
void QGCToolbox::setChildToolboxes(void)
......@@ -95,11 +98,13 @@ void QGCToolbox::setChildToolboxes(void)
_followMe->setToolbox(this);
_qgcPositionManager->setToolbox(this);
_videoManager->setToolbox(this);
_mavlinkLogManager->setToolbox(this);
}
QGCToolbox::~QGCToolbox()
{
delete _videoManager;
delete _mavlinkLogManager;
delete _audioOutput;
delete _autopilotPluginManager;
delete _factSystem;
......
......@@ -32,6 +32,7 @@ class QGCImageProvider;
class UASMessageHandler;
class QGCPositionManager;
class VideoManager;
class MavlinkLogManager;
/// This is used to manage all of our top level services/tools
class QGCToolbox {
......@@ -56,6 +57,8 @@ public:
FollowMe* followMe(void) { return _followMe; }
QGCPositionManager* qgcPositionManager(void) { return _qgcPositionManager; }
VideoManager* videoManager(void) { return _videoManager; }
MavlinkLogManager* mavlinkLogManager(void) { return _mavlinkLogManager; }
#ifndef __mobile__
GPSManager* gpsManager(void) { return _gpsManager; }
#endif
......@@ -83,6 +86,7 @@ private:
FollowMe* _followMe;
QGCPositionManager* _qgcPositionManager;
VideoManager* _videoManager;
MavlinkLogManager* _mavlinkLogManager;
friend class QGCApplication;
};
......
......@@ -44,6 +44,7 @@ QGroundControlQmlGlobal::QGroundControlQmlGlobal(QGCApplication* app)
, _qgcPositionManager(NULL)
, _missionCommandTree(NULL)
, _videoManager(NULL)
, _mavlinkLogManager(NULL)
, _virtualTabletJoystick(false)
, _baseFontPointSize(0.0)
{
......@@ -60,7 +61,6 @@ QGroundControlQmlGlobal::~QGroundControlQmlGlobal()
}
void QGroundControlQmlGlobal::setToolbox(QGCToolbox* toolbox)
{
QGCTool::setToolbox(toolbox);
......@@ -72,9 +72,9 @@ void QGroundControlQmlGlobal::setToolbox(QGCToolbox* toolbox)
_qgcPositionManager = toolbox->qgcPositionManager();
_missionCommandTree = toolbox->missionCommandTree();
_videoManager = toolbox->videoManager();
_mavlinkLogManager = toolbox->mavlinkLogManager();
}
void QGroundControlQmlGlobal::saveGlobalSetting (const QString& key, const QString& value)
{
QSettings settings;
......
......@@ -72,6 +72,7 @@ public:
Q_PROPERTY(QGCPositionManager* qgcPositionManger READ qgcPositionManger CONSTANT)
Q_PROPERTY(MissionCommandTree* missionCommandTree READ missionCommandTree CONSTANT)
Q_PROPERTY(VideoManager* videoManager READ videoManager CONSTANT)
Q_PROPERTY(MavlinkLogManager* mavlinkLogManager READ mavlinkLogManager CONSTANT)
Q_PROPERTY(qreal zOrderTopMost READ zOrderTopMost CONSTANT) ///< z order for top most items, toolbar, main window sub view
Q_PROPERTY(qreal zOrderWidgets READ zOrderWidgets CONSTANT) ///< z order value to widgets, for example: zoom controls, hud widgetss
......@@ -166,6 +167,7 @@ public:
QGCPositionManager* qgcPositionManger () { return _qgcPositionManager; }
MissionCommandTree* missionCommandTree () { return _missionCommandTree; }
VideoManager* videoManager () { return _videoManager; }
MavlinkLogManager* mavlinkLogManager () { return _mavlinkLogManager; }
qreal zOrderTopMost () { return 1000; }
qreal zOrderWidgets () { return 100; }
......@@ -237,6 +239,7 @@ private:
QGCPositionManager* _qgcPositionManager;
MissionCommandTree* _missionCommandTree;
VideoManager* _videoManager;
MavlinkLogManager* _mavlinkLogManager;
bool _virtualTabletJoystick;
qreal _baseFontPointSize;
......
/****************************************************************************
*
* (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.
*
****************************************************************************/
#include "MavlinkLogManager.h"
#include "QGCApplication.h"
#include <QQmlContext>
#include <QQmlProperty>
#include <QQmlEngine>
#include <QtQml>
#include <QSettings>
#include <QHttpPart>
#include <QNetworkReply>
#include <QFile>
#include <QFileInfo>
#define kTimeOutMilliseconds 1000
QGC_LOGGING_CATEGORY(MavlinkLogManagerLog, "MavlinkLogManagerLog")
static const char* kEmailAddressKey = "MavlinkLogEmail";
static const char* kDescriptionsKey = "MavlinkLogDescription";
static const char* kDefaultDescr = "QGroundControl Session";
static const char* kPx4URLKey = "MavlinkLogURL";
static const char* kDefaultPx4URL = "http://logs.px4.io/upload";
static const char* kEnableAutoUploadKey = "EnableAutoUploadKey";
static const char* kEnableAutoStartKey = "EnableAutoStartKey";
static const char* kEnableDeletetKey = "EnableDeleteKey";
static const char* kUlogExtension = ".ulg";
static const char* kSidecarExtension = ".uploaded";
//-----------------------------------------------------------------------------
MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& filePath, bool newFile)
: _manager(manager)
, _size(0)
, _selected(false)
, _uploading(false)
, _progress(0)
, _writing(false)
, _uploaded(false)
{
QFileInfo fi(filePath);
_name = fi.baseName();
if(!newFile) {
_size = (quint32)fi.size();
QString sideCar = filePath;
sideCar.replace(kUlogExtension, kSidecarExtension);
QFileInfo sc(sideCar);
_uploaded = sc.exists();
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setSize(quint32 size)
{
_size = size;
emit sizeChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setSelected(bool selected)
{
_selected = selected;
emit selectedChanged();
emit _manager->selectedCountChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setUploading(bool uploading)
{
_uploading = uploading;
emit uploadingChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setProgress(qreal progress)
{
_progress = progress;
emit progressChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setWriting(bool writing)
{
_writing = writing;
emit writingChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogFiles::setUploaded(bool uploaded)
{
_uploaded = uploaded;
emit uploadedChanged();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MavlinkLogProcessor::MavlinkLogProcessor()
: _fd(NULL)
, _written(0)
, _sequence(-1)
, _numDrops(0)
, _gotHeader(false)
, _error(false)
, _record(NULL)
{
}
//-----------------------------------------------------------------------------
MavlinkLogProcessor::~MavlinkLogProcessor()
{
close();
}
//-----------------------------------------------------------------------------
void
MavlinkLogProcessor::close()
{
if(_fd) {
fclose(_fd);
_fd = NULL;
}
}
//-----------------------------------------------------------------------------
bool
MavlinkLogProcessor::valid()
{
return (_fd != NULL) && (_record != NULL);
}
//-----------------------------------------------------------------------------
bool
MavlinkLogProcessor::create(MavlinkLogManager* manager, const QString path, uint8_t id)
{
_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) {
_record = new MavlinkLogFiles(manager, _fileName, true);
_record->setWriting(true);
_sequence = -1;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool
MavlinkLogProcessor::_checkSequence(uint16_t seq, int& num_drops)
{
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
MavlinkLogProcessor::_writeData(void* data, int len)
{
if(!_error) {
_error = fwrite(data, 1, len, _fd) != (size_t)len;
if(!_error) {
_written += len;
if(_record) {
_record->setSize(_written);
}
} else {
qCDebug(MavlinkLogManagerLog) << "File IO error:" << len << "bytes into" << _fileName;
}
}
}
//-----------------------------------------------------------------------------
QByteArray
MavlinkLogProcessor::_writeUlogMessage(QByteArray& data)
{
//-- 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;
}
return data;
}
//-----------------------------------------------------------------------------
bool
MavlinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
{
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.
qCCritical(MavlinkLogManagerLog) << "Corrupt log header. Canceling log download.";
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;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MavlinkLogManager::MavlinkLogManager(QGCApplication* app)
: QGCTool(app)
, _enableAutoUpload(true)
, _enableAutoStart(true)
, _nam(NULL)
, _currentLogfile(NULL)
, _vehicle(NULL)
, _logRunning(false)
, _loggingDisabled(false)
, _logProcessor(NULL)
, _deleteAfterUpload(false)
, _loggingCmdTryCount(0)
{
//-- 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());
setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
setEnableAutoStart(settings.value(kEnableAutoStartKey, true).toBool());
setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
//-- Logging location
_logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
_logPath += "/MavlinkLogs";
if(!QDir(_logPath).exists()) {
if(!QDir().mkpath(_logPath)) {
qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log download path:" << _logPath;
_loggingDisabled = true;
}
}
if(!_loggingDisabled) {
//-- Load current list of logs
QString filter = "*";
filter += kUlogExtension;
QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
while(it.hasNext()) {
_insertNewLog(new MavlinkLogFiles(this, it.next()));
}
qCDebug(MavlinkLogManagerLog) << "Mavlink logs directory:" << _logPath;
}
}
//-----------------------------------------------------------------------------
MavlinkLogManager::~MavlinkLogManager()
{
_logFiles.clear();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setToolbox(QGCToolbox* toolbox)
{
QGCTool::setToolbox(toolbox);
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
qmlRegisterUncreatableType<MavlinkLogManager>("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only");
if(!_loggingDisabled) {
connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged);
connect(&_ackTimer, &QTimer::timeout, this, &MavlinkLogManager::_processCmdAck);
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setEmailAddress(QString email)
{
_emailAddress = email;
QSettings settings;
settings.setValue(kEmailAddressKey, email);
emit emailAddressChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setDescription(QString description)
{
_description = description;
QSettings settings;
settings.setValue(kDescriptionsKey, description);
emit descriptionChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setUploadURL(QString url)
{
_uploadURL = url;
if(_uploadURL.isEmpty()) {
_uploadURL = kDefaultPx4URL;
}
QSettings settings;
settings.setValue(kPx4URLKey, _uploadURL);
emit uploadURLChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setEnableAutoUpload(bool enable)
{
_enableAutoUpload = enable;
QSettings settings;
settings.setValue(kEnableAutoUploadKey, enable);
emit enableAutoUploadChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setEnableAutoStart(bool enable)
{
_enableAutoStart = enable;
QSettings settings;
settings.setValue(kEnableAutoStartKey, enable);
emit enableAutoStartChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::setDeleteAfterUpload(bool enable)
{
_deleteAfterUpload = enable;
QSettings settings;
settings.setValue(kEnableDeletetKey, enable);
emit deleteAfterUploadChanged();
}
//-----------------------------------------------------------------------------
bool
MavlinkLogManager::uploading()
{
return _currentLogfile != NULL;
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::uploadLog()
{
if(_currentLogfile) {
_currentLogfile->setUploading(false);
}
for(int i = 0; i < _logFiles.count(); i++) {
_currentLogfile = qobject_cast<MavlinkLogFiles*>(_logFiles.get(i));
Q_ASSERT(_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;
}
}
}
_currentLogfile = NULL;
emit uploadingChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_insertNewLog(MavlinkLogFiles* newLog)
{
//-- Simpler than trying to sort this thing
int count = _logFiles.count();
if(!count) {
_logFiles.append(newLog);
} else {
for(int i = 0; i < count; i++) {
MavlinkLogFiles* f = qobject_cast<MavlinkLogFiles*>(_logFiles.get(i));
if(newLog->name() < f->name()) {
_logFiles.insert(i, newLog);
return;
}
}
_logFiles.append(newLog);
}
}
//-----------------------------------------------------------------------------
int
MavlinkLogManager::_getFirstSelected()
{
for(int i = 0; i < _logFiles.count(); i++) {
MavlinkLogFiles* f = qobject_cast<MavlinkLogFiles*>(_logFiles.get(i));
Q_ASSERT(f);
if(f->selected()) {
return i;
}
}
return -1;
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::deleteLog()
{
while (true) {
int idx = _getFirstSelected();
if(idx < 0) {
break;
}
MavlinkLogFiles* log = qobject_cast<MavlinkLogFiles*>(_logFiles.get(idx));
_deleteLog(log);
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_deleteLog(MavlinkLogFiles* log)
{
QString filePath = _makeFilename(log->name());
QFile gone(filePath);
if(!gone.remove()) {
qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath;
}
//-- Remove sidecar file (if any)
filePath.replace(kUlogExtension, kSidecarExtension);
QFile sgone(filePath);
if(sgone.exists()) {
sgone.remove();
}
//-- Remove file from list and delete record
_logFiles.removeOne(log);
delete log;
emit logFilesChanged();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::cancelUpload()
{
for(int i = 0; i < _logFiles.count(); i++) {
MavlinkLogFiles* pLogFile = qobject_cast<MavlinkLogFiles*>(_logFiles.get(i));
Q_ASSERT(pLogFile);
if(pLogFile->selected() && pLogFile != _currentLogfile) {
pLogFile->setSelected(false);
}
}
if(_currentLogfile) {
emit abortUpload();
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::startLogging()
{
if(_vehicle) {
if(_createNewLog()) {
_vehicle->startMavlinkLog();
_logRunning = true;
_loggingCmdTryCount = 0;
_ackTimer.start(kTimeOutMilliseconds);
emit logRunningChanged();
}
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::stopLogging()
{
if(_vehicle) {
//-- Tell vehicle to stop sending logs
_vehicle->stopMavlinkLog();
}
if(_logProcessor) {
_logProcessor->close();
if(_logProcessor->record()) {
_logProcessor->record()->setWriting(false);
if(_enableAutoUpload) {
//-- Queue log for auto upload (set selected flag)
_logProcessor->record()->setSelected(true);
if(!uploading()) {
uploadLog();
}
}
}
delete _logProcessor;
_logProcessor = NULL;
_logRunning = false;
if(_vehicle) {
//-- Setup a timer to make sure vehicle received the command
_loggingCmdTryCount = 0;
_ackTimer.start(kTimeOutMilliseconds);
}
emit logRunningChanged();
}
}
//-----------------------------------------------------------------------------
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
MavlinkLogManager::_sendLog(const QString& logFile)
{
QString defaultDescription = _description;
if(_description.isEmpty()) {
qCWarning(MavlinkLogManagerLog) << "Log description missing. Using defaults.";
defaultDescription = kDefaultDescr;
}
if(_emailAddress.isEmpty()) {
qCCritical(MavlinkLogManagerLog) << "User email missing.";
return false;
}
if(_uploadURL.isEmpty()) {
qCCritical(MavlinkLogManagerLog) << "Upload URL missing.";
return false;
}
QFileInfo fi(logFile);
if(!fi.exists()) {
qCCritical(MavlinkLogManagerLog) << "Log file missing:" << logFile;
return false;
}
QFile* file = new QFile(logFile);
if(!file || !file->open(QIODevice::ReadOnly)) {
if(file) {
delete file;
}
qCCritical(MavlinkLogManagerLog) << "Could not open log file:" << logFile;
return false;
}
if(!_nam) {
_nam = new QNetworkAccessManager(this);
}
QNetworkProxy savedProxy = _nam->proxy();
QNetworkProxy tempProxy;
tempProxy.setType(QNetworkProxy::DefaultProxy);
_nam->setProxy(tempProxy);
//-- Build POST request
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
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);
#if QT_VERSION > 0x050600
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
#endif
QNetworkReply* reply = _nam->post(request, multiPart);
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);
multiPart->setParent(reply);
qCDebug(MavlinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
_nam->setProxy(savedProxy);
return true;
}
//-----------------------------------------------------------------------------
bool
MavlinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
{
qCDebug(MavlinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
emit readyRead(data);
return http_code == 200;
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_dataAvailable()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(!reply) {
return;
}
QByteArray data = reply->readAll();
qCDebug(MavlinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_uploadFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(!reply) {
return;
}
const int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray data = reply->readAll();
if(_processUploadResponse(http_code, data)) {
qCDebug(MavlinkLogManagerLog) << "Log uploaded.";
emit succeed();
if(_deleteAfterUpload) {
if(_currentLogfile) {
_deleteLog(_currentLogfile);
_currentLogfile = NULL;
}
} 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);
}
}
}
} else {
qCWarning(MavlinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
emit failed();
}
reply->deleteLater();
//-- Next (if any)
uploadLog();
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
if(bytesTotal) {
qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
if(_currentLogfile) {
_currentLogfile->setProgress(progress);
}
}
qCDebug(MavlinkLogManagerLog) << bytesSent << "of" << bytesTotal;
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
{
//-- 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.
// For now, we only handle one log download at a time.
// Disconnect the previous one (if any)
if(_vehicle) {
disconnect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged);
disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData);
disconnect(_vehicle, &Vehicle::commandLongAck, this, &MavlinkLogManager::_commandLongAck);
_vehicle = NULL;
//-- Stop logging (if that's the case)
stopLogging();
emit canStartLogChanged();
}
// Connect new system
if(vehicle) {
_vehicle = vehicle;
connect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged);
connect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData);
connect(_vehicle, &Vehicle::commandLongAck, this, &MavlinkLogManager::_commandLongAck);
emit canStartLogChanged();
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_processCmdAck()
{
if(_loggingCmdTryCount++ > 3) {
_ackTimer.stop();
//-- Give up
if(_logRunning) {
qCWarning(MavlinkLogManagerLog) << "Start MAVLink log command had no response.";
_discardLog();
} else {
qCWarning(MavlinkLogManagerLog) << "Stop MAVLink log command had no response.";
}
} else {
if(_vehicle) {
if(_logRunning) {
_vehicle->startMavlinkLog();
qCWarning(MavlinkLogManagerLog) << "Start MAVLink log command sent again.";
} else {
_vehicle->stopMavlinkLog();
qCWarning(MavlinkLogManagerLog) << "Stop MAVLink log command sent again.";
}
_ackTimer.start(kTimeOutMilliseconds);
} else {
//-- Vehicle went away on us
_ackTimer.stop();
}
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t first_message, QByteArray data, bool /*acked*/)
{
//-- Disable timer if we got a message before an ACK for the start command
if(_logRunning) {
_ackTimer.stop();
}
if(_logProcessor && _logProcessor->valid()) {
if(!_logProcessor->processStreamData(sequence, first_message, data)) {
qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _logProcessor->fileName();
delete _logProcessor;
_logProcessor = NULL;
_logRunning = false;
_vehicle->stopMavlinkLog();
emit logRunningChanged();
}
} else {
qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected.";
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_commandLongAck(uint8_t /*compID*/, uint16_t command, uint8_t result)
{
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...
qCWarning(MavlinkLogManagerLog) << "Stop MAVLink log command failed.";
} else {
//-- Could not start logging for some reason.
qCWarning(MavlinkLogManagerLog) << "Start MAVLink log command failed.";
_discardLog();
}
}
}
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_discardLog()
{
//-- Delete (empty) log file (and record)
if(_logProcessor) {
_logProcessor->close();
if(_logProcessor->record()) {
_deleteLog(_logProcessor->record());
}
delete _logProcessor;
_logProcessor = NULL;
}
_logRunning = false;
emit logRunningChanged();
}
//-----------------------------------------------------------------------------
bool
MavlinkLogManager::_createNewLog()
{
if(_logProcessor) {
delete _logProcessor;
_logProcessor = NULL;
}
_logProcessor = new MavlinkLogProcessor;
if(_logProcessor->create(this, _logPath, _vehicle->id())) {
_insertNewLog(_logProcessor->record());
emit logFilesChanged();
} else {
qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _logProcessor->fileName();
delete _logProcessor;
_logProcessor = NULL;
}
return _logProcessor != NULL;
}
//-----------------------------------------------------------------------------
void
MavlinkLogManager::_armedChanged(bool armed)
{
if(_vehicle) {
if(armed) {
if(_enableAutoStart) {
startLogging();
}
} else {
if(_logRunning && _enableAutoStart) {
stopLogging();
}
}
}
}
//-----------------------------------------------------------------------------
QString
MavlinkLogManager::_makeFilename(const QString& baseName)
{
QString filePath = _logPath;
filePath += "/";
filePath += baseName;
filePath += kUlogExtension;
return filePath;
}
/****************************************************************************
*
* (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.
*
****************************************************************************/
#ifndef MavlinkLogManager_H
#define MavlinkLogManager_H
#include <QObject>
#include "QmlObjectListModel.h"
#include "QGCLoggingCategory.h"
#include "QGCToolbox.h"
#include "Vehicle.h"
Q_DECLARE_LOGGING_CATEGORY(MavlinkLogManagerLog)
class QNetworkAccessManager;
class MavlinkLogManager;
//-----------------------------------------------------------------------------
class MavlinkLogFiles : public QObject
{
Q_OBJECT
public:
MavlinkLogFiles (MavlinkLogManager* manager, const QString& filePath, bool newFile = false);
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(quint32 size READ size NOTIFY sizeChanged)
Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
Q_PROPERTY(bool writing READ writing NOTIFY writingChanged)
Q_PROPERTY(bool uploaded READ uploaded NOTIFY uploadedChanged)
QString name () { return _name; }
quint32 size () { return _size; }
bool selected () { return _selected; }
bool uploading () { return _uploading; }
qreal progress () { return _progress; }
bool writing () { return _writing; }
bool uploaded () { return _uploaded; }
void setSelected (bool selected);
void setUploading (bool uploading);
void setProgress (qreal progress);
void setWriting (bool writing);
void setSize (quint32 size);
void setUploaded (bool uploaded);
signals:
void sizeChanged ();
void selectedChanged ();
void uploadingChanged ();
void progressChanged ();
void writingChanged ();
void uploadedChanged ();
private:
MavlinkLogManager* _manager;
QString _name;
quint32 _size;
bool _selected;
bool _uploading;
qreal _progress;
bool _writing;
bool _uploaded;
};
//-----------------------------------------------------------------------------
class MavlinkLogProcessor
{
public:
MavlinkLogProcessor();
~MavlinkLogProcessor();
void close ();
bool valid ();
bool create (MavlinkLogManager *manager, const QString path, uint8_t id);
MavlinkLogFiles* record () { return _record; }
QString fileName () { return _fileName; }
bool processStreamData(uint16_t _sequence, uint8_t first_message, QByteArray data);
private:
bool _checkSequence(uint16_t seq, int &num_drops);
QByteArray _writeUlogMessage(QByteArray &data);
void _writeData(void* data, int len);
private:
FILE* _fd;
quint32 _written;
int _sequence;
int _numDrops;
bool _gotHeader;
bool _error;
QByteArray _ulogMessage;
QString _fileName;
MavlinkLogFiles* _record;
};
//-----------------------------------------------------------------------------
class MavlinkLogManager : public QGCTool
{
Q_OBJECT
public:
MavlinkLogManager (QGCApplication* app);
~MavlinkLogManager ();
Q_PROPERTY(QString emailAddress READ emailAddress WRITE setEmailAddress NOTIFY emailAddressChanged)
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
Q_PROPERTY(QString uploadURL READ uploadURL WRITE setUploadURL NOTIFY uploadURLChanged)
Q_PROPERTY(bool enableAutoUpload READ enableAutoUpload WRITE setEnableAutoUpload NOTIFY enableAutoUploadChanged)
Q_PROPERTY(bool enableAutoStart READ enableAutoStart WRITE setEnableAutoStart NOTIFY enableAutoStartChanged)
Q_PROPERTY(bool deleteAfterUpload READ deleteAfterUpload WRITE setDeleteAfterUpload NOTIFY deleteAfterUploadChanged)
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
Q_PROPERTY(bool logRunning READ logRunning NOTIFY logRunningChanged)
Q_PROPERTY(bool canStartLog READ canStartLog NOTIFY canStartLogChanged)
Q_PROPERTY(QmlObjectListModel* logFiles READ logFiles NOTIFY logFilesChanged)
Q_INVOKABLE void uploadLog ();
Q_INVOKABLE void deleteLog ();
Q_INVOKABLE void cancelUpload ();
Q_INVOKABLE void startLogging ();
Q_INVOKABLE void stopLogging ();
QString emailAddress () { return _emailAddress; }
QString description () { return _description; }
QString uploadURL () { return _uploadURL; }
bool enableAutoUpload () { return _enableAutoUpload; }
bool enableAutoStart () { return _enableAutoStart; }
bool uploading ();
bool logRunning () { return _logRunning; }
bool canStartLog () { return _vehicle != NULL; }
bool deleteAfterUpload () { return _deleteAfterUpload; }
QmlObjectListModel* logFiles () { return &_logFiles; }
void setEmailAddress (QString email);
void setDescription (QString description);
void setUploadURL (QString url);
void setEnableAutoUpload (bool enable);
void setEnableAutoStart (bool enable);
void setDeleteAfterUpload(bool enable);
// Override from QGCTool
void setToolbox (QGCToolbox *toolbox);
signals:
void emailAddressChanged ();
void descriptionChanged ();
void uploadURLChanged ();
void enableAutoUploadChanged ();
void enableAutoStartChanged ();
void logFilesChanged ();
void selectedCountChanged ();
void uploadingChanged ();
void readyRead (QByteArray data);
void failed ();
void succeed ();
void abortUpload ();
void logRunningChanged ();
void canStartLogChanged ();
void deleteAfterUploadChanged ();
private slots:
void _uploadFinished ();
void _dataAvailable ();
void _uploadProgress (qint64 bytesSent, qint64 bytesTotal);
void _activeVehicleChanged (Vehicle* vehicle);
void _mavlinkLogData (Vehicle* vehicle, uint8_t target_system, uint8_t target_component, uint16_t sequence, uint8_t first_message, QByteArray data, bool acked);
void _armedChanged (bool armed);
void _commandLongAck (uint8_t compID, uint16_t command, uint8_t result);
void _processCmdAck ();
private:
bool _sendLog (const QString& logFile);
bool _processUploadResponse (int http_code, QByteArray &data);
bool _createNewLog ();
int _getFirstSelected ();
void _insertNewLog (MavlinkLogFiles* newLog);
void _deleteLog (MavlinkLogFiles* log);
void _discardLog ();
QString _makeFilename (const QString& baseName);
private:
QString _description;
QString _emailAddress;
QString _uploadURL;
QString _logPath;
bool _enableAutoUpload;
bool _enableAutoStart;
QNetworkAccessManager* _nam;
QmlObjectListModel _logFiles;
MavlinkLogFiles* _currentLogfile;
Vehicle* _vehicle;
bool _logRunning;
bool _loggingDisabled;
MavlinkLogProcessor* _logProcessor;
bool _deleteAfterUpload;
int _loggingCmdTryCount;
QTimer _ackTimer;
};
#endif
......@@ -400,6 +400,7 @@ Vehicle::resetCounters()
void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
{
if (message.sysid != _id && message.sysid != 0) {
return;
}
......@@ -480,7 +481,7 @@ void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t mes
_handleCommandAck(message);
break;
case MAVLINK_MSG_ID_AUTOPILOT_VERSION:
_handleAutopilotVersion(message);
_handleAutopilotVersion(link, message);
break;
case MAVLINK_MSG_ID_WIND_COV:
_handleWindCov(message);
......@@ -488,6 +489,12 @@ void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t mes
case MAVLINK_MSG_ID_HIL_ACTUATOR_CONTROLS:
_handleHilActuatorControls(message);
break;
case MAVLINK_MSG_ID_LOGGING_DATA:
_handleMavlinkLoggingData(message);
break;
case MAVLINK_MSG_ID_LOGGING_DATA_ACKED:
_handleMavlinkLoggingDataAcked(message);
break;
// Following are ArduPilot dialect messages
......@@ -501,11 +508,17 @@ void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t mes
_uas->receiveMessage(message);
}
void Vehicle::_handleAutopilotVersion(mavlink_message_t& message)
void Vehicle::_handleAutopilotVersion(LinkInterface *link, mavlink_message_t& message)
{
mavlink_autopilot_version_t autopilotVersion;
mavlink_msg_autopilot_version_decode(&message, &autopilotVersion);
bool isMavlink2 = (autopilotVersion.capabilities & MAV_PROTOCOL_CAPABILITY_MAVLINK2) != 0;
if(isMavlink2) {
mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(link->mavlinkChannel());
mavlinkStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
}
if (autopilotVersion.flight_sw_version != 0) {
int majorVersion, minorVersion, patchVersion;
FIRMWARE_VERSION_TYPE versionType;
......@@ -549,9 +562,14 @@ void Vehicle::_handleCommandAck(mavlink_message_t& message)
emit commandLongAck(message.compid, ack.command, ack.result);
if (ack.command == MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES) {
// Disregard failures
return;
// Disregard failures for these (handled above)
switch (ack.command) {
case MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES:
case MAV_CMD_LOGGING_START:
case MAV_CMD_LOGGING_STOP:
return;
default:
break;
}
QString commandName = qgcApp()->toolbox()->missionCommandTree()->friendlyName((MAV_CMD)ack.command);
......@@ -1959,6 +1977,62 @@ VehicleGPSFactGroup::VehicleGPSFactGroup(QObject* parent)
_courseOverGroundFact.setRawValue(std::numeric_limits<float>::quiet_NaN());
}
//-----------------------------------------------------------------------------
void
Vehicle::startMavlinkLog()
{
doCommandLong(defaultComponentId(), MAV_CMD_LOGGING_START);
}
//-----------------------------------------------------------------------------
void
Vehicle::stopMavlinkLog()
{
doCommandLong(defaultComponentId(), MAV_CMD_LOGGING_STOP);
}
//-----------------------------------------------------------------------------
void
Vehicle::_ackMavlinkLogData(uint16_t sequence)
{
mavlink_message_t msg;
mavlink_logging_ack_t ack;
ack.sequence = sequence;
ack.target_component = defaultComponentId();
ack.target_system = id();
mavlink_msg_logging_ack_encode_chan(
_mavlink->getSystemId(),
_mavlink->getComponentId(),
priorityLink()->mavlinkChannel(),
&msg,
&ack);
sendMessageOnLink(priorityLink(), msg);
}
//-----------------------------------------------------------------------------
void
Vehicle::_handleMavlinkLoggingData(mavlink_message_t& message)
{
mavlink_logging_data_t log;
mavlink_msg_logging_data_decode(&message, &log);
emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence,
log.first_message_offset, QByteArray((const char*)log.data, log.length), false);
}
//-----------------------------------------------------------------------------
void
Vehicle::_handleMavlinkLoggingDataAcked(mavlink_message_t& message)
{
mavlink_logging_data_t log;
mavlink_msg_logging_data_decode(&message, &log);
_ackMavlinkLogData(log.sequence);
emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence,
log.first_message_offset, QByteArray((const char*)log.data, log.length), true);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void VehicleGPSFactGroup::setVehicle(Vehicle* vehicle)
{
_vehicle = vehicle;
......
......@@ -485,6 +485,10 @@ public:
int flowImageIndex() { return _flowImageIndex; }
//-- Mavlink Logging
void startMavlinkLog();
void stopMavlinkLog();
/// Requests the specified data stream from the vehicle
/// @param stream Stream which is being requested
/// @param rate Rate at which to send stream in Hz
......@@ -638,6 +642,9 @@ signals:
void mavlinkScaledImu2(mavlink_message_t message);
void mavlinkScaledImu3(mavlink_message_t message);
// Mavlink Log Download
void mavlinkLogData (Vehicle* vehicle, uint8_t target_system, uint8_t target_component, uint16_t sequence, uint8_t first_message, QByteArray data, bool acked);
private slots:
void _mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message);
void _linkInactiveOrDeleted(LinkInterface* link);
......@@ -685,7 +692,7 @@ private:
void _handleVibration(mavlink_message_t& message);
void _handleExtendedSysState(mavlink_message_t& message);
void _handleCommandAck(mavlink_message_t& message);
void _handleAutopilotVersion(mavlink_message_t& message);
void _handleAutopilotVersion(LinkInterface* link, mavlink_message_t& message);
void _handleHilActuatorControls(mavlink_message_t& message);
void _missionManagerError(int errorCode, const QString& errorMsg);
void _geoFenceManagerError(int errorCode, const QString& errorMsg);
......@@ -695,6 +702,9 @@ private:
void _connectionActive(void);
void _say(const QString& text);
QString _vehicleIdSpeech(void);
void _handleMavlinkLoggingData(mavlink_message_t& message);
void _handleMavlinkLoggingDataAcked(mavlink_message_t& message);
void _ackMavlinkLogData(uint16_t sequence);
private:
int _id; ///< Mavlink system id
......
......@@ -73,7 +73,7 @@ MAVLinkProtocol::MAVLinkProtocol(QGCApplication* app)
MAVLinkProtocol::~MAVLinkProtocol()
{
storeSettings();
#ifndef __mobile__
_closeLogFile();
#endif
......@@ -162,7 +162,7 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
if (!_linkMgr->links()->contains(link)) {
return;
}
// receiveMutex.lock();
mavlink_message_t message;
mavlink_status_t status;
......@@ -214,6 +214,8 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
{
decodedFirstPacket = true;
/*
* Handled in Vehicle.cc now.
mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(mavlinkChannel);
if (!(mavlinkStatus->flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1) && (mavlinkStatus->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1)) {
qDebug() << "switch to mavlink 2.0" << mavlinkStatus << mavlinkChannel << mavlinkStatus->flags;
......@@ -222,6 +224,7 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
qDebug() << "switch to mavlink 1.0" << mavlinkStatus << mavlinkChannel << mavlinkStatus->flags;
mavlinkStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
}
*/
if(message.msgid == MAVLINK_MSG_ID_RADIO_STATUS)
{
......@@ -255,7 +258,7 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
#ifndef __mobile__
// Log data
if (!_logSuspendError && !_logSuspendReplay && _tempLogFile.isOpen()) {
uint8_t buf[MAVLINK_MAX_PACKET_LEN+sizeof(quint64)];
......@@ -280,7 +283,7 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
_stopLogging();
_logSuspendError = true;
}
// Check for the vehicle arming going by. This is used to trigger log save.
if (!_logPromptForSave && message.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
mavlink_heartbeat_t state;
......@@ -412,7 +415,7 @@ bool MAVLinkProtocol::_closeLogFile(void)
return true;
}
}
return false;
}
......@@ -458,11 +461,11 @@ void MAVLinkProtocol::_stopLogging(void)
void MAVLinkProtocol::checkForLostLogFiles(void)
{
QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
QString filter(QString("*.%1").arg(_logFileExtension));
QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
qDebug() << "Orphaned log file count" << fileInfoList.count();
foreach(const QFileInfo fileInfo, fileInfoList) {
qDebug() << "Orphaned log file" << fileInfo.filePath();
if (fileInfo.size() == 0) {
......@@ -488,10 +491,10 @@ void MAVLinkProtocol::suspendLogForReplay(bool suspend)
void MAVLinkProtocol::deleteTempLogFiles(void)
{
QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
QString filter(QString("*.%1").arg(_logFileExtension));
QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
foreach(const QFileInfo fileInfo, fileInfoList) {
QFile::remove(fileInfo.filePath());
}
......
......@@ -307,6 +307,7 @@ signals:
// Log Download Signals
void logEntry (UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num);
void logData (UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data);
};
Q_DECLARE_INTERFACE(UASInterface, "org.qgroundcontrol/1.0")
......
......@@ -25,56 +25,461 @@ Rectangle {
color: qgcPal.window
anchors.fill: parent
property real _labelWidth: ScreenTools.defaultFontPixelWidth * 28
property real _valueWidth: ScreenTools.defaultFontPixelWidth * 24
property int _selectedCount: 0
property real _columnSpacing: ScreenTools.defaultFontPixelHeight * 0.25
property bool _uploadedSelected: false
QGCPalette { id: qgcPal }
Connections {
target: QGroundControl.mavlinkLogManager
onSelectedCountChanged: {
_uploadedSelected = false
var selected = 0
for(var i = 0; i < QGroundControl.mavlinkLogManager.logFiles.count; i++) {
var logFile = QGroundControl.mavlinkLogManager.logFiles.get(i)
if(logFile.selected) {
selected++
//-- If an uploaded file is selected, disable "Upload" button
if(logFile.uploaded) {
_uploadedSelected = true
}
}
}
_selectedCount = selected
}
}
MessageDialog {
id: emptyEmailDialog
visible: false
icon: StandardIcon.Warning
standardButtons: StandardButton.Close
title: qsTr("MAVLink Logging")
text: qsTr("Please enter an email address before uploading MAVLink log files.")
}
QGCFlickable {
clip: true
anchors.fill: parent
anchors.margins: ScreenTools.defaultFontPixelWidth
contentHeight: settingsColumn.height
contentWidth: settingsColumn.width
flickableDirection: Flickable.VerticalFlick
Column {
id: settingsColumn
spacing: ScreenTools.defaultFontPixelHeight
width: __mavlinkRoot.width
spacing: ScreenTools.defaultFontPixelHeight * 0.5
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.left: parent.left
anchors.top: parent.top
//-----------------------------------------------------------------
//-- System ID
Row {
spacing: ScreenTools.defaultFontPixelWidth
//-- Ground Station
Item {
width: __mavlinkRoot.width * 0.8
height: gcsLabel.height
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCLabel {
text: qsTr("Ground Station MavLink System ID:")
anchors.verticalCenter: parent.verticalCenter
id: gcsLabel
text: qsTr("Ground Station")
font.family: ScreenTools.demiboldFontFamily
}
QGCTextField {
id: sysidField
text: QGroundControl.mavlinkSystemID.toString()
width: ScreenTools.defaultFontPixelWidth * 6
inputMethodHints: Qt.ImhFormattedNumbersOnly
anchors.verticalCenter: parent.verticalCenter
onEditingFinished: {
QGroundControl.mavlinkSystemID = parseInt(sysidField.text)
}
Rectangle {
height: gcsColumn.height + (ScreenTools.defaultFontPixelHeight * 2)
width: __mavlinkRoot.width * 0.8
color: qgcPal.windowShade
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
Column {
id: gcsColumn
spacing: _columnSpacing
anchors.centerIn: parent
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCLabel {
width: _labelWidth
anchors.baseline: sysidField.baseline
text: qsTr("MavLink System ID:")
}
QGCTextField {
id: sysidField
text: QGroundControl.mavlinkSystemID.toString()
width: _valueWidth
inputMethodHints: Qt.ImhFormattedNumbersOnly
anchors.verticalCenter: parent.verticalCenter
onEditingFinished: {
QGroundControl.mavlinkSystemID = parseInt(sysidField.text)
}
}
}
//-----------------------------------------------------------------
//-- Mavlink Heartbeats
QGCCheckBox {
text: qsTr("Emit heartbeat")
checked: QGroundControl.multiVehicleManager.gcsHeartBeatEnabled
onClicked: {
QGroundControl.multiVehicleManager.gcsHeartBeatEnabled = checked
}
}
//-----------------------------------------------------------------
//-- Mavlink Version Check
QGCCheckBox {
text: qsTr("Only accept MAVs with same protocol version")
checked: QGroundControl.isVersionCheckEnabled
onClicked: {
QGroundControl.isVersionCheckEnabled = checked
}
}
}
}
//-----------------------------------------------------------------
//-- Mavlink Heartbeats
QGCCheckBox {
text: qsTr("Emit heartbeat")
checked: QGroundControl.multiVehicleManager.gcsHeartBeatEnabled
onClicked: {
QGroundControl.multiVehicleManager.gcsHeartBeatEnabled = checked
//-- Mavlink Logging
Item {
width: __mavlinkRoot.width * 0.8
height: mavlogLabel.height
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCLabel {
id: mavlogLabel
text: qsTr("Vehicle Mavlink Logging")
font.family: ScreenTools.demiboldFontFamily
}
}
Rectangle {
height: mavlogColumn.height + (ScreenTools.defaultFontPixelHeight * 2)
width: __mavlinkRoot.width * 0.8
color: qgcPal.windowShade
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
Column {
id: mavlogColumn
width: gcsColumn.width
spacing: _columnSpacing
anchors.centerIn: parent
//-----------------------------------------------------------------
//-- Manual Start/Stop
Row {
spacing: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCLabel {
width: _labelWidth
text: qsTr("Manual Start/Stop:")
anchors.verticalCenter: parent.verticalCenter
}
QGCButton {
text: qsTr("Start Logging")
width: (_valueWidth * 0.5) - (ScreenTools.defaultFontPixelWidth * 0.5)
enabled: !QGroundControl.mavlinkLogManager.logRunning && QGroundControl.mavlinkLogManager.canStartLog
onClicked: QGroundControl.mavlinkLogManager.startLogging()
anchors.verticalCenter: parent.verticalCenter
}
QGCButton {
text: qsTr("Stop Logging")
width: (_valueWidth * 0.5) - (ScreenTools.defaultFontPixelWidth * 0.5)
enabled: QGroundControl.mavlinkLogManager.logRunning
onClicked: QGroundControl.mavlinkLogManager.stopLogging()
anchors.verticalCenter: parent.verticalCenter
}
}
//-----------------------------------------------------------------
//-- Enable auto log on arming
QGCCheckBox {
text: qsTr("Enable automatic logging")
checked: QGroundControl.mavlinkLogManager.enableAutoStart
onClicked: {
QGroundControl.mavlinkLogManager.enableAutoStart = checked
}
}
}
}
//-----------------------------------------------------------------
//-- Mavlink Version Check
QGCCheckBox {
text: qsTr("Only accept MAVs with same protocol version")
checked: QGroundControl.isVersionCheckEnabled
onClicked: {
QGroundControl.isVersionCheckEnabled = checked
//-- Mavlink Logging
Item {
width: __mavlinkRoot.width * 0.8
height: logLabel.height
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCLabel {
id: logLabel
text: qsTr("Mavlink Log Uploads")
font.family: ScreenTools.demiboldFontFamily
}
}
Rectangle {
height: logColumn.height + (ScreenTools.defaultFontPixelHeight * 2)
width: __mavlinkRoot.width * 0.8
color: qgcPal.windowShade
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
Column {
id: logColumn
spacing: _columnSpacing
anchors.centerIn: parent
//-----------------------------------------------------------------
//-- Email address Field
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCLabel {
width: _labelWidth
anchors.baseline: emailField.baseline
text: qsTr("Email address for Log Upload:")
}
QGCTextField {
id: emailField
text: QGroundControl.mavlinkLogManager.emailAddress
width: _valueWidth
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhEmailCharactersOnly
anchors.verticalCenter: parent.verticalCenter
onEditingFinished: {
QGroundControl.mavlinkLogManager.emailAddress = emailField.text
if(emailField.text === "") {
autoUploadCheck.checked = false
QGroundControl.mavlinkLogManager.enableAutoUpload = false
console.log("forcing enableAutoUpload to false")
}
}
}
}
//-----------------------------------------------------------------
//-- Description Field
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCLabel {
width: _labelWidth
anchors.baseline: descField.baseline
text: qsTr("Default Description:")
}
QGCTextField {
id: descField
text: QGroundControl.mavlinkLogManager.description
width: _valueWidth
anchors.verticalCenter: parent.verticalCenter
onEditingFinished: {
QGroundControl.mavlinkLogManager.description = descField.text
}
}
}
//-----------------------------------------------------------------
//-- Upload URL
Row {
spacing: ScreenTools.defaultFontPixelWidth
QGCLabel {
width: _labelWidth
anchors.baseline: urlField.baseline
text: qsTr("Default Upload URL")
}
QGCTextField {
id: urlField
text: QGroundControl.mavlinkLogManager.uploadURL
width: _valueWidth
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhUrlCharactersOnly
anchors.verticalCenter: parent.verticalCenter
onEditingFinished: {
QGroundControl.mavlinkLogManager.uploadURL = urlField.text
}
}
}
//-----------------------------------------------------------------
//-- Automatic Upload
QGCCheckBox {
id: autoUploadCheck
text: qsTr("Enable automatic log uploads")
checked: QGroundControl.mavlinkLogManager.enableAutoUpload
onClicked: {
QGroundControl.mavlinkLogManager.emailAddress = emailField.text
if(checked && QGroundControl.mavlinkLogManager.emailAddress === "") {
checked = false
emptyEmailDialog.open()
} else {
QGroundControl.mavlinkLogManager.enableAutoUpload = checked
}
}
}
//-----------------------------------------------------------------
//-- Delete log after upload
QGCCheckBox {
text: qsTr("Delete log file after uploading")
checked: QGroundControl.mavlinkLogManager.deleteAfterUpload
enabled: autoUploadCheck.checked
onClicked: {
QGroundControl.mavlinkLogManager.deleteAfterUpload = checked
}
}
}
}
//-----------------------------------------------------------------
//-- Log Files
Item {
width: __mavlinkRoot.width * 0.8
height: logFilesLabel.height
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCLabel {
id: logFilesLabel
text: qsTr("Saved Log Files")
font.family: ScreenTools.demiboldFontFamily
}
}
Rectangle {
height: logFilesColumn.height + (ScreenTools.defaultFontPixelHeight * 2)
width: __mavlinkRoot.width * 0.8
color: qgcPal.windowShade
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
Column {
id: logFilesColumn
spacing: _columnSpacing * 4
anchors.centerIn: parent
width: ScreenTools.defaultFontPixelWidth * 68
Rectangle {
width: ScreenTools.defaultFontPixelWidth * 64
height: ScreenTools.defaultFontPixelHeight * 14
anchors.horizontalCenter: parent.horizontalCenter
color: qgcPal.window
border.color: qgcPal.text
border.width: 0.5
ListView {
width: ScreenTools.defaultFontPixelWidth * 56
height: ScreenTools.defaultFontPixelHeight * 12
anchors.centerIn: parent
orientation: ListView.Vertical
model: QGroundControl.mavlinkLogManager.logFiles
clip: true
delegate: Rectangle {
width: ScreenTools.defaultFontPixelWidth * 52
height: selectCheck.height
color: qgcPal.window
Row {
width: ScreenTools.defaultFontPixelWidth * 50
anchors.centerIn: parent
spacing: ScreenTools.defaultFontPixelWidth
QGCCheckBox {
id: selectCheck
width: ScreenTools.defaultFontPixelWidth * 4
checked: object.selected
enabled: !object.writing && !object.uploading
anchors.verticalCenter: parent.verticalCenter
onClicked: {
object.selected = checked
}
}
QGCLabel {
text: object.name
width: ScreenTools.defaultFontPixelWidth * 28
color: object.writing ? qgcPal.warningText : qgcPal.text
anchors.verticalCenter: parent.verticalCenter
}
QGCLabel {
text: Number(object.size).toLocaleString(Qt.locale(), 'f', 0)
visible: !object.uploading && !object.uploaded
width: ScreenTools.defaultFontPixelWidth * 20;
color: object.writing ? qgcPal.warningText : qgcPal.text
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
QGCLabel {
text: "Uploaded"
visible: object.uploaded
width: ScreenTools.defaultFontPixelWidth * 20;
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
ProgressBar {
visible: object.uploading && !object.uploaded
width: ScreenTools.defaultFontPixelWidth * 20;
height: ScreenTools.defaultFontPixelHeight
anchors.verticalCenter: parent.verticalCenter
minimumValue: 0
maximumValue: 100
value: object.progress * 100.0
}
}
}
}
}
Row {
spacing: ScreenTools.defaultFontPixelWidth
anchors.horizontalCenter: parent.horizontalCenter
QGCButton {
text: "Check All"
enabled: !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning
onClicked: {
for(var i = 0; i < QGroundControl.mavlinkLogManager.logFiles.count; i++) {
var logFile = QGroundControl.mavlinkLogManager.logFiles.get(i)
logFile.selected = true
}
}
}
QGCButton {
text: "Check None"
enabled: !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning
onClicked: {
for(var i = 0; i < QGroundControl.mavlinkLogManager.logFiles.count; i++) {
var logFile = QGroundControl.mavlinkLogManager.logFiles.get(i)
logFile.selected = false
}
}
}
QGCButton {
text: "Delete Selected"
enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning
onClicked: deleteDialog.open()
MessageDialog {
id: deleteDialog
visible: false
icon: StandardIcon.Warning
standardButtons: StandardButton.Yes | StandardButton.No
title: qsTr("Delete Selected Log Files")
text: qsTr("Confirm deleting selected log files?")
onYes: {
QGroundControl.mavlinkLogManager.deleteLog()
}
}
}
QGCButton {
text: "Upload Selected"
enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning && !_uploadedSelected
visible: !QGroundControl.mavlinkLogManager.uploading
onClicked: {
QGroundControl.mavlinkLogManager.emailAddress = emailField.text
if(QGroundControl.mavlinkLogManager.emailAddress === "")
emptyEmailDialog.open()
else
uploadDialog.open()
}
MessageDialog {
id: uploadDialog
visible: false
icon: StandardIcon.Question
standardButtons: StandardButton.Yes | StandardButton.No
title: qsTr("Upload Selected Log Files")
text: qsTr("Confirm uploading selected log files?")
onYes: {
QGroundControl.mavlinkLogManager.uploadLog()
}
}
}
QGCButton {
text: "Cancel"
enabled: QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning
visible: QGroundControl.mavlinkLogManager.uploading
onClicked: cancelDialog.open()
MessageDialog {
id: cancelDialog
visible: false
icon: StandardIcon.Warning
standardButtons: StandardButton.Yes | StandardButton.No
title: qsTr("Cancel Upload")
text: qsTr("Confirm canceling the upload process?")
onYes: {
QGroundControl.mavlinkLogManager.cancelUpload()
}
}
}
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment