From dfbaf7f28031703de6861ff5e3d753988ce3df5a Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Sun, 16 Oct 2016 23:06:45 -0400 Subject: [PATCH 01/10] Core Mavlink log handler. --- qgroundcontrol.pro | 2 + src/QGCToolbox.cc | 5 + src/QGCToolbox.h | 4 + src/QmlControls/QGroundControlQmlGlobal.cc | 4 +- src/QmlControls/QGroundControlQmlGlobal.h | 3 + src/uas/MavlinkLogManager.cc | 349 ++++++++++++++++++++++++++++ src/uas/MavlinkLogManager.h | 130 +++++++++++ src/ui/preferences/MavlinkSettings.qml | 358 ++++++++++++++++++++++++++--- 8 files changed, 825 insertions(+), 30 deletions(-) create mode 100644 src/uas/MavlinkLogManager.cc create mode 100644 src/uas/MavlinkLogManager.h diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 9dfad86..c8adcb5 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -334,6 +334,7 @@ HEADERS += \ src/uas/UAS.h \ src/uas/UASInterface.h \ src/uas/UASMessageHandler.h \ + src/uas/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/uas/MavlinkLogManager.cc \ src/ui/toolbar/MainToolBarController.cc \ src/AutoPilotPlugins/PX4/PX4AirframeLoader.cc \ src/AutoPilotPlugins/APM/APMAirframeLoader.cc \ diff --git a/src/QGCToolbox.cc b/src/QGCToolbox.cc index 5c63154..fd569b7 100644 --- a/src/QGCToolbox.cc +++ b/src/QGCToolbox.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; diff --git a/src/QGCToolbox.h b/src/QGCToolbox.h index 540c917..1b0080d 100644 --- a/src/QGCToolbox.h +++ b/src/QGCToolbox.h @@ -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; }; diff --git a/src/QmlControls/QGroundControlQmlGlobal.cc b/src/QmlControls/QGroundControlQmlGlobal.cc index abd618b..c249191 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.cc +++ b/src/QmlControls/QGroundControlQmlGlobal.cc @@ -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; diff --git a/src/QmlControls/QGroundControlQmlGlobal.h b/src/QmlControls/QGroundControlQmlGlobal.h index 6918021..b23823f 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.h +++ b/src/QmlControls/QGroundControlQmlGlobal.h @@ -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; diff --git a/src/uas/MavlinkLogManager.cc b/src/uas/MavlinkLogManager.cc new file mode 100644 index 0000000..2b45f8a --- /dev/null +++ b/src/uas/MavlinkLogManager.cc @@ -0,0 +1,349 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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* kEnableAutologKey= "EnableAutologKey"; + + +//----------------------------------------------------------------------------- +MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager *manager, const QString& filePath) + : _manager(manager) + , _size(0) + , _selected(false) + , _uploading(false) + , _progress(0) +{ + QFileInfo fi(filePath); + _name = fi.baseName(); + _size = (quint32)fi.size(); +} + +//----------------------------------------------------------------------------- +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(); +} + +//----------------------------------------------------------------------------- +MavlinkLogManager::MavlinkLogManager(QGCApplication* app) + : QGCTool(app) + , _enableAutolog(true) + , _nam(NULL) + , _currentLogfile(NULL) +{ + //-- 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()); + setEnableAutolog(settings.value(kEnableAutologKey, true).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; + } + } + //-- Load current list of logs + QDirIterator it(_logPath, QStringList() << "*.ulg", QDir::Files); + while(it.hasNext()) { + _logFiles.append(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("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); + + // _uploadURL = "http://192.168.1.21/px4"; + // _uploadURL = "http://192.168.1.9:8080"; + // _emailAddress = "gus.grubba.com"; + // _description = "Test from QGroundControl - Discard"; + // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); + +} + +//----------------------------------------------------------------------------- +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::setEnableAutolog(bool enable) +{ + _enableAutolog = enable; + QSettings settings; + settings.setValue(kEnableAutologKey, enable); + emit enableAutologChanged(); +} + +//----------------------------------------------------------------------------- +bool +MavlinkLogManager::busy() +{ + return _currentLogfile != NULL; +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::uploadLog() +{ + if(_currentLogfile) { + _currentLogfile->setUploading(false); + } + for(int i = 0; i < _logFiles.count(); i++ ) { + _currentLogfile = qobject_cast(_logFiles.get(i)); + Q_ASSERT(_currentLogfile); + if(_currentLogfile->selected()) { + _currentLogfile->setSelected(false); + _currentLogfile->setUploading(true); + _currentLogfile->setProgress(0.0); + QString filePath = _logPath; + filePath += "/"; + filePath += _currentLogfile->name(); + filePath += ".ulg"; + _sendLog(filePath); + emit busyChanged(); + return; + } + } + _currentLogfile = NULL; + emit busyChanged(); +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::deleteLog() +{ + //-- TODO +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::cancelUpload() +{ + for(int i = 0; i < _logFiles.count(); i++ ) { + MavlinkLogFiles* pLogFile = qobject_cast(_logFiles.get(i)); + Q_ASSERT(pLogFile); + if(pLogFile->selected() && pLogFile != _currentLogfile) { + pLogFile->setSelected(false); + } + } + if(_currentLogfile) { + emit abortUpload(); + } +} + +//----------------------------------------------------------------------------- +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); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + 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(sender()); + if(!reply) { + return; + } + QByteArray data = reply->readAll(); + qCDebug(MavlinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data); +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_uploadFinished() +{ + QNetworkReply *reply = qobject_cast(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(); + } else { + qCDebug(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; +} diff --git a/src/uas/MavlinkLogManager.h b/src/uas/MavlinkLogManager.h new file mode 100644 index 0000000..de41bc1 --- /dev/null +++ b/src/uas/MavlinkLogManager.h @@ -0,0 +1,130 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * 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 + +#include "QmlObjectListModel.h" +#include "QGCLoggingCategory.h" +#include "QGCToolbox.h" + +Q_DECLARE_LOGGING_CATEGORY(MavlinkLogManagerLog) + +class QNetworkAccessManager; +class MavlinkLogManager; + +//----------------------------------------------------------------------------- +class MavlinkLogFiles : public QObject +{ + Q_OBJECT +public: + MavlinkLogFiles (MavlinkLogManager* manager, const QString& filePath); + + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(quint32 size READ size CONSTANT) + 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) + + QString name () { return _name; } + quint32 size () { return _size; } + bool selected () { return _selected; } + bool uploading () { return _uploading; } + qreal progress () { return _progress; } + + void setSelected (bool selected); + void setUploading (bool uploading); + void setProgress (qreal progress); + +signals: + void selectedChanged (); + void uploadingChanged (); + void progressChanged (); + +private: + MavlinkLogManager* _manager; + QString _name; + quint32 _size; + bool _selected; + bool _uploading; + qreal _progress; +}; + +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 enableAutolog READ enableAutolog WRITE setEnableAutolog NOTIFY enableAutologChanged) + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(QmlObjectListModel* logFiles READ logFiles NOTIFY logFilesChanged) + + Q_INVOKABLE void uploadLog (); + Q_INVOKABLE void deleteLog (); + Q_INVOKABLE void cancelUpload (); + + QString emailAddress () { return _emailAddress; } + QString description () { return _description; } + QString uploadURL () { return _uploadURL; } + bool enableAutolog () { return _enableAutolog; } + bool busy (); + + QmlObjectListModel* logFiles () { return &_logFiles; } + + void setEmailAddress (QString email); + void setDescription (QString description); + void setUploadURL (QString url); + void setEnableAutolog (bool enable); + + // Override from QGCTool + void setToolbox (QGCToolbox *toolbox); + +signals: + void emailAddressChanged (); + void descriptionChanged (); + void uploadURLChanged (); + void enableAutologChanged (); + void logFilesChanged (); + void selectedCountChanged (); + void busyChanged (); + void readyRead (QByteArray data); + void failed (); + void succeed (); + void abortUpload (); + +private slots: + void _uploadFinished (); + void _dataAvailable (); + void _uploadProgress (qint64 bytesSent, qint64 bytesTotal); + +private: + bool _sendLog (const QString& logFile); + bool _processUploadResponse (int http_code, QByteArray &data); + +private: + QString _description; + QString _emailAddress; + QString _uploadURL; + QString _logPath; + bool _enableAutolog; + QNetworkAccessManager* _nam; + QmlObjectListModel _logFiles; + MavlinkLogFiles* _currentLogfile; +}; + +#endif diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index 37ac332..9f0cf1e 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -25,8 +25,36 @@ Rectangle { color: qgcPal.window anchors.fill: parent + property real _labelWidth: ScreenTools.defaultFontPixelWidth * 28 + property real _valueWidth: ScreenTools.defaultFontPixelWidth * 24 + property int _selectedCount: 0 + QGCPalette { id: qgcPal } + Connections { + target: QGroundControl.mavlinkLogManager + onSelectedCountChanged: { + var selected = 0 + for(var i = 0; i < QGroundControl.mavlinkLogManager.logFiles.count; i++) { + var logFile = QGroundControl.mavlinkLogManager.logFiles.get(i) + console.log(logFile.selected) + if(logFile.selected) + selected++ + } + _selectedCount = selected + console.log(_selectedCount) + } + } + + MessageDialog { + id: emptyEmailDialog + visible: false + icon: StandardIcon.Warning + standardButtons: StandardButton.Close + title: qsTr("Uploading Log Files") + text: qsTr("Please enter an email address before uploading log files.") + } + QGCFlickable { clip: true anchors.fill: parent @@ -36,45 +64,319 @@ Rectangle { 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: ScreenTools.defaultFontPixelWidth + 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: logLabel.height + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCLabel { + id: logLabel + text: qsTr("Vehicle Mavlink Logging") + 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: ScreenTools.defaultFontPixelWidth + 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 + } + } + } + //----------------------------------------------------------------- + //-- 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 { + text: qsTr("Enable automatic log uploads") + checked: QGroundControl.mavlinkLogManager.enableAutolog + enabled: emailField.text !== "" && urlField !== "" + onClicked: { + QGroundControl.mavlinkLogManager.enableAutolog = checked + } + } } } //----------------------------------------------------------------- - //-- Mavlink Version Check - QGCCheckBox { - text: qsTr("Only accept MAVs with same protocol version") - checked: QGroundControl.isVersionCheckEnabled - onClicked: { - QGroundControl.isVersionCheckEnabled = 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: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + Rectangle { + width: ScreenTools.defaultFontPixelWidth * 52 + height: ScreenTools.defaultFontPixelHeight * 10 + anchors.horizontalCenter: parent.horizontalCenter + color: qgcPal.windowShade + border.color: qgcPal.text + border.width: 0.5 + ListView { + width: ScreenTools.defaultFontPixelWidth * 50 + height: ScreenTools.defaultFontPixelHeight * 9 + anchors.centerIn: parent + orientation: ListView.Vertical + model: QGroundControl.mavlinkLogManager.logFiles + delegate: Rectangle { + width: ScreenTools.defaultFontPixelWidth * 48 + height: ScreenTools.defaultFontPixelHeight * 1.25 + color: index % 2 == 0 ? qgcPal.window : qgcPal.windowShade + anchors.horizontalCenter: parent.horizontalCenter; + Row { + width: ScreenTools.defaultFontPixelWidth * 46 + anchors.centerIn: parent + spacing: ScreenTools.defaultFontPixelWidth + QGCCheckBox { + width: ScreenTools.defaultFontPixelWidth * 4 + checked: object.selected + onClicked: { + object.selected = checked + } + } + QGCLabel { + text: object.name + width: ScreenTools.defaultFontPixelWidth * 20 + } + QGCLabel { + text: object.size + visible: !object.uploading + width: ScreenTools.defaultFontPixelWidth * 20; + horizontalAlignment: Text.AlignRight + } + ProgressBar { + visible: object.uploading + 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.busy + 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.busy + 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.busy + 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.busy + visible: !QGroundControl.mavlinkLogManager.busy + 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.busy + visible: QGroundControl.mavlinkLogManager.busy + 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() + } + } + } + } } } } From f91c5bef0813f0aabd4939278d0c3e1c536b1868 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Mon, 24 Oct 2016 21:32:22 -0400 Subject: [PATCH 02/10] Setting up Mavlink logging Start/Stop (and receiving the data) --- src/Vehicle/Vehicle.cc | 63 +++++++++++++++++ src/Vehicle/Vehicle.h | 10 +++ src/uas/MavlinkLogManager.cc | 120 +++++++++++++++++++++++++++++---- src/uas/MavlinkLogManager.h | 39 ++++++++--- src/uas/UASInterface.h | 1 + src/ui/preferences/MavlinkSettings.qml | 57 +++++++++++++++- 6 files changed, 264 insertions(+), 26 deletions(-) diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 09480d7..ff154aa 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -400,6 +400,7 @@ Vehicle::resetCounters() void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message) { + if (message.sysid != _id && message.sysid != 0) { return; } @@ -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 @@ -1959,6 +1966,62 @@ VehicleGPSFactGroup::VehicleGPSFactGroup(QObject* parent) _courseOverGroundFact.setRawValue(std::numeric_limits::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) +{ + qDebug() << "MAVLINK_MSG_ID_LOGGING_DATA"; + mavlink_logging_data_t log; + mavlink_msg_logging_data_decode(&message, &log); + emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence, log.length, log.first_message_offset, log.data, false); +} + +//----------------------------------------------------------------------------- +void +Vehicle::_handleMavlinkLoggingDataAcked(mavlink_message_t& message) +{ + qDebug() << "MAVLINK_MSG_ID_LOGGING_DATA_ACKED"; + 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.length, log.first_message_offset, log.data, true); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + void VehicleGPSFactGroup::setVehicle(Vehicle* vehicle) { _vehicle = vehicle; diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index c2b3bfb..808d650 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -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 length, uint8_t first_message, const uint8_t* data, bool acked); + private slots: void _mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message); void _linkInactiveOrDeleted(LinkInterface* link); @@ -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 diff --git a/src/uas/MavlinkLogManager.cc b/src/uas/MavlinkLogManager.cc index 2b45f8a..9563dd1 100644 --- a/src/uas/MavlinkLogManager.cc +++ b/src/uas/MavlinkLogManager.cc @@ -21,13 +21,13 @@ 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* kEnableAutologKey= "EnableAutologKey"; - +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"; //----------------------------------------------------------------------------- MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager *manager, const QString& filePath) @@ -70,16 +70,20 @@ MavlinkLogFiles::setProgress(qreal progress) //----------------------------------------------------------------------------- MavlinkLogManager::MavlinkLogManager(QGCApplication* app) : QGCTool(app) - , _enableAutolog(true) + , _enableAutoUpload(true) + , _enableAutoStart(true) , _nam(NULL) , _currentLogfile(NULL) + , _vehicle(NULL) + , _logRunning(false) { //-- 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()); - setEnableAutolog(settings.value(kEnableAutologKey, true).toBool()); + setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool()); + setEnableAutoStart(settings.value(kEnableAutoStartKey, true).toBool()); //-- Logging location _logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); _logPath += "/MavlinkLogs"; @@ -109,6 +113,7 @@ MavlinkLogManager::setToolbox(QGCToolbox *toolbox) QGCTool::setToolbox(toolbox); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); qmlRegisterUncreatableType("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); + connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); // _uploadURL = "http://192.168.1.21/px4"; // _uploadURL = "http://192.168.1.9:8080"; @@ -153,12 +158,22 @@ MavlinkLogManager::setUploadURL(QString url) //----------------------------------------------------------------------------- void -MavlinkLogManager::setEnableAutolog(bool enable) +MavlinkLogManager::setEnableAutoUpload(bool enable) +{ + _enableAutoUpload = enable; + QSettings settings; + settings.setValue(kEnableAutoUploadKey, enable); + emit enableAutoUploadChanged(); +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::setEnableAutoStart(bool enable) { - _enableAutolog = enable; + _enableAutoStart = enable; QSettings settings; - settings.setValue(kEnableAutologKey, enable); - emit enableAutologChanged(); + settings.setValue(kEnableAutoStartKey, enable); + emit enableAutoStartChanged(); } //----------------------------------------------------------------------------- @@ -219,6 +234,28 @@ MavlinkLogManager::cancelUpload() } //----------------------------------------------------------------------------- +void +MavlinkLogManager::startLogging() +{ + if(_vehicle) { + _vehicle->startMavlinkLog(); + _logRunning = true; + emit logRunningChanged(); + } +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::stopLogging() +{ + if(_vehicle) { + _vehicle->stopMavlinkLog(); + _logRunning = false; + emit logRunningChanged(); + } +} + +//----------------------------------------------------------------------------- QHttpPart create_form_part(const QString& name, const QString& value) { @@ -347,3 +384,60 @@ MavlinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal) } 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. + + // Disconnect the previous one (if any) + if (_vehicle) { + disconnect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); + disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); + _vehicle = NULL; + emit canStartLogChanged(); + } + // Connect new system + if (vehicle) + { + _vehicle = vehicle; + connect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); + connect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); + emit canStartLogChanged(); + } +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_mavlinkLogData(Vehicle * /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) +{ + Q_UNUSED(data); + qDebug() << "Mavlink Log:" << sequence << length << first_message; +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_armedChanged(bool armed) +{ + if(_vehicle) { + if(armed) { + if(_enableAutoStart) { + _vehicle->startMavlinkLog(); + _logRunning = true; + emit logRunningChanged(); + } + } else { + if(_logRunning && _enableAutoStart) { + _vehicle->stopMavlinkLog(); + emit logRunningChanged(); + if(_enableAutoUpload) { + //-- TODO: Queue log for auto upload + } + } + } + } +} diff --git a/src/uas/MavlinkLogManager.h b/src/uas/MavlinkLogManager.h index de41bc1..3ef3dc5 100644 --- a/src/uas/MavlinkLogManager.h +++ b/src/uas/MavlinkLogManager.h @@ -16,6 +16,7 @@ #include "QmlObjectListModel.h" #include "QGCLoggingCategory.h" #include "QGCToolbox.h" +#include "Vehicle.h" Q_DECLARE_LOGGING_CATEGORY(MavlinkLogManagerLog) @@ -67,29 +68,38 @@ 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 enableAutolog READ enableAutolog WRITE setEnableAutolog NOTIFY enableAutologChanged) - Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) - Q_PROPERTY(QmlObjectListModel* logFiles READ logFiles NOTIFY logFilesChanged) + 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 busy READ busy NOTIFY busyChanged) + 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 enableAutolog () { return _enableAutolog; } + bool enableAutoUpload () { return _enableAutoUpload; } + bool enableAutoStart () { return _enableAutoStart; } bool busy (); + bool logRunning () { return _logRunning; } + bool canStartLog () { return _vehicle != NULL; } QmlObjectListModel* logFiles () { return &_logFiles; } void setEmailAddress (QString email); void setDescription (QString description); void setUploadURL (QString url); - void setEnableAutolog (bool enable); + void setEnableAutoUpload (bool enable); + void setEnableAutoStart (bool enable); // Override from QGCTool void setToolbox (QGCToolbox *toolbox); @@ -98,7 +108,8 @@ signals: void emailAddressChanged (); void descriptionChanged (); void uploadURLChanged (); - void enableAutologChanged (); + void enableAutoUploadChanged (); + void enableAutoStartChanged (); void logFilesChanged (); void selectedCountChanged (); void busyChanged (); @@ -106,11 +117,16 @@ signals: void failed (); void succeed (); void abortUpload (); + void logRunningChanged (); + void canStartLogChanged (); 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 length, uint8_t first_message, const uint8_t* data, bool acked); + void _armedChanged (bool armed); private: bool _sendLog (const QString& logFile); @@ -121,10 +137,13 @@ private: QString _emailAddress; QString _uploadURL; QString _logPath; - bool _enableAutolog; + bool _enableAutoUpload; + bool _enableAutoStart; QNetworkAccessManager* _nam; QmlObjectListModel _logFiles; MavlinkLogFiles* _currentLogfile; + Vehicle* _vehicle; + bool _logRunning; }; #endif diff --git a/src/uas/UASInterface.h b/src/uas/UASInterface.h index cf022f5..10ad42f 100644 --- a/src/uas/UASInterface.h +++ b/src/uas/UASInterface.h @@ -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") diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index 9f0cf1e..b73fc56 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -132,12 +132,63 @@ Rectangle { //-- 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: ScreenTools.defaultFontPixelWidth + anchors.centerIn: parent + //----------------------------------------------------------------- + //-- Enable auto log on arming + QGCCheckBox { + text: qsTr("Enable automatic logging start when vehicle is armed") + checked: QGroundControl.mavlinkLogManager.enableAutoStart + onClicked: { + QGroundControl.mavlinkLogManager.enableAutoStart = checked + } + } + //----------------------------------------------------------------- + //-- Manual Start/Stop + Row { + spacing: ScreenTools.defaultFontPixelWidth + anchors.horizontalCenter: parent.horizontalCenter + QGCButton { + text: "Start Logging" + enabled: !QGroundControl.mavlinkLogManager.logRunning && QGroundControl.mavlinkLogManager.canStartLog + onClicked: QGroundControl.mavlinkLogManager.startLogging() + } + QGCButton { + text: "Stop Logging" + enabled: QGroundControl.mavlinkLogManager.logRunning + onClicked: QGroundControl.mavlinkLogManager.stopLogging() + } + } + } + } + //----------------------------------------------------------------- + //-- Mavlink Logging + Item { + width: __mavlinkRoot.width * 0.8 height: logLabel.height anchors.margins: ScreenTools.defaultFontPixelWidth anchors.horizontalCenter: parent.horizontalCenter QGCLabel { id: logLabel - text: qsTr("Vehicle Mavlink Logging") + text: qsTr("Mavlink Log Uploads") font.family: ScreenTools.demiboldFontFamily } } @@ -214,10 +265,10 @@ Rectangle { //-- Automatic Upload QGCCheckBox { text: qsTr("Enable automatic log uploads") - checked: QGroundControl.mavlinkLogManager.enableAutolog + checked: QGroundControl.mavlinkLogManager.enableAutoUpload enabled: emailField.text !== "" && urlField !== "" onClicked: { - QGroundControl.mavlinkLogManager.enableAutolog = checked + QGroundControl.mavlinkLogManager.enableAutoUpload = checked } } } From 4d23912aa19b92ed78312d3dd2768ac4f33b89c9 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Tue, 25 Oct 2016 05:46:46 -0400 Subject: [PATCH 03/10] Receiving and writing logs. Forcing QGC to use Mavlink V2 if vehicle supports it. --- src/Vehicle/Vehicle.cc | 12 ++- src/Vehicle/Vehicle.h | 2 +- src/comm/MAVLinkProtocol.cc | 21 ++-- src/uas/MavlinkLogManager.cc | 178 ++++++++++++++++++++++++--------- src/uas/MavlinkLogManager.h | 6 ++ src/ui/preferences/MavlinkSettings.qml | 23 ++--- 6 files changed, 168 insertions(+), 74 deletions(-) diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index ff154aa..6a5044b 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -481,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); @@ -508,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; @@ -2002,7 +2008,6 @@ Vehicle::_ackMavlinkLogData(uint16_t sequence) void Vehicle::_handleMavlinkLoggingData(mavlink_message_t& message) { - qDebug() << "MAVLINK_MSG_ID_LOGGING_DATA"; mavlink_logging_data_t log; mavlink_msg_logging_data_decode(&message, &log); emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence, log.length, log.first_message_offset, log.data, false); @@ -2012,7 +2017,6 @@ Vehicle::_handleMavlinkLoggingData(mavlink_message_t& message) void Vehicle::_handleMavlinkLoggingDataAcked(mavlink_message_t& message) { - qDebug() << "MAVLINK_MSG_ID_LOGGING_DATA_ACKED"; mavlink_logging_data_t log; mavlink_msg_logging_data_decode(&message, &log); _ackMavlinkLogData(log.sequence); diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 808d650..372a848 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -692,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); diff --git a/src/comm/MAVLinkProtocol.cc b/src/comm/MAVLinkProtocol.cc index 3e23813..bf1ce98 100644 --- a/src/comm/MAVLinkProtocol.cc +++ b/src/comm/MAVLinkProtocol.cc @@ -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()); } diff --git a/src/uas/MavlinkLogManager.cc b/src/uas/MavlinkLogManager.cc index 9563dd1..17b1e8e 100644 --- a/src/uas/MavlinkLogManager.cc +++ b/src/uas/MavlinkLogManager.cc @@ -30,7 +30,7 @@ static const char* kEnableAutoUploadKey = "EnableAutoUploadKey"; static const char* kEnableAutoStartKey = "EnableAutoStartKey"; //----------------------------------------------------------------------------- -MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager *manager, const QString& filePath) +MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& filePath) : _manager(manager) , _size(0) , _selected(false) @@ -76,6 +76,9 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) , _currentLogfile(NULL) , _vehicle(NULL) , _logRunning(false) + , _loggingDisabled(false) + , _currentSavingFileFd(NULL) + , _sequence(0) { //-- Get saved settings QSettings settings; @@ -90,14 +93,17 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) if(!QDir(_logPath).exists()) { if(QDir().mkpath(_logPath)) { qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log download path:" << _logPath; + _loggingDisabled = true; } } - //-- Load current list of logs - QDirIterator it(_logPath, QStringList() << "*.ulg", QDir::Files); - while(it.hasNext()) { - _logFiles.append(new MavlinkLogFiles(this, it.next())); + if(!_loggingDisabled) { + //-- Load current list of logs + QDirIterator it(_logPath, QStringList() << "*.ulg", QDir::Files); + while(it.hasNext()) { + _logFiles.append(new MavlinkLogFiles(this, it.next())); + } + qCDebug(MavlinkLogManagerLog) << "Mavlink logs directory:" << _logPath; } - qCDebug(MavlinkLogManagerLog) << "Mavlink logs directory:" << _logPath; } //----------------------------------------------------------------------------- @@ -108,19 +114,19 @@ MavlinkLogManager::~MavlinkLogManager() //----------------------------------------------------------------------------- void -MavlinkLogManager::setToolbox(QGCToolbox *toolbox) +MavlinkLogManager::setToolbox(QGCToolbox* toolbox) { - QGCTool::setToolbox(toolbox); - QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); - qmlRegisterUncreatableType("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); - connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); - - // _uploadURL = "http://192.168.1.21/px4"; - // _uploadURL = "http://192.168.1.9:8080"; - // _emailAddress = "gus.grubba.com"; - // _description = "Test from QGroundControl - Discard"; - // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); - + QGCTool::setToolbox(toolbox); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + qmlRegisterUncreatableType("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); + if(!_loggingDisabled) { + connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); + } + // _uploadURL = "http://192.168.1.21/px4"; + // _uploadURL = "http://192.168.1.9:8080"; + // _emailAddress = "gus.grubba.com"; + // _description = "Test from QGroundControl - Discard"; + // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); } //----------------------------------------------------------------------------- @@ -190,7 +196,7 @@ MavlinkLogManager::uploadLog() if(_currentLogfile) { _currentLogfile->setUploading(false); } - for(int i = 0; i < _logFiles.count(); i++ ) { + for(int i = 0; i < _logFiles.count(); i++) { _currentLogfile = qobject_cast(_logFiles.get(i)); Q_ASSERT(_currentLogfile); if(_currentLogfile->selected()) { @@ -211,17 +217,48 @@ MavlinkLogManager::uploadLog() } //----------------------------------------------------------------------------- +int +MavlinkLogManager::_getFirstSelected() +{ + for(int i = 0; i < _logFiles.count(); i++) { + MavlinkLogFiles* f = qobject_cast(_logFiles.get(i)); + Q_ASSERT(f); + if(f->selected()) { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- void MavlinkLogManager::deleteLog() { - //-- TODO + while (true) { + int idx = _getFirstSelected(); + if(idx < 0) { + break; + } + MavlinkLogFiles* f = qobject_cast(_logFiles.get(idx)); + QString filePath = _logPath; + filePath += "/"; + filePath += f->name(); + filePath += ".ulg"; + QFile gone(filePath); + if(!gone.remove()) { + qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath; + } + _logFiles.removeAt(idx); + delete f; + emit logFilesChanged(); + } } //----------------------------------------------------------------------------- void MavlinkLogManager::cancelUpload() { - for(int i = 0; i < _logFiles.count(); i++ ) { + for(int i = 0; i < _logFiles.count(); i++) { MavlinkLogFiles* pLogFile = qobject_cast(_logFiles.get(i)); Q_ASSERT(pLogFile); if(pLogFile->selected() && pLogFile != _currentLogfile) { @@ -238,9 +275,11 @@ void MavlinkLogManager::startLogging() { if(_vehicle) { - _vehicle->startMavlinkLog(); - _logRunning = true; - emit logRunningChanged(); + if(_createNewLog()) { + _vehicle->startMavlinkLog(); + _logRunning = true; + emit logRunningChanged(); + } } } @@ -252,6 +291,17 @@ MavlinkLogManager::stopLogging() _vehicle->stopMavlinkLog(); _logRunning = false; emit logRunningChanged(); + if(_currentSavingFileFd) { + fclose(_currentSavingFileFd); + _logFiles.append(new MavlinkLogFiles(this, _currentSavingFileStr)); + emit logFilesChanged(); + _currentSavingFileFd = NULL; + _currentSavingFileStr.clear(); + //-- TODO: If auto upload is set, schedule it + if(_enableAutoUpload) { + //-- Queue log for auto upload + } + } } } @@ -287,21 +337,23 @@ MavlinkLogManager::_sendLog(const QString& logFile) qCCritical(MavlinkLogManagerLog) << "Log file missing:" << logFile; return false; } - QFile *file = new QFile(logFile); + QFile* file = new QFile(logFile); if(!file || !file->open(QIODevice::ReadOnly)) { - if (file) delete file; + if(file) { + delete file; + } qCCritical(MavlinkLogManagerLog) << "Could not open log file:" << logFile; return false; } if(!_nam) { - _nam = new QNetworkAccessManager(this); + _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); + 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"); @@ -319,7 +371,7 @@ MavlinkLogManager::_sendLog(const QString& logFile) file->setParent(multiPart); QNetworkRequest request(_uploadURL); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - QNetworkReply *reply = _nam->post(request, multiPart); + 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); @@ -332,7 +384,7 @@ MavlinkLogManager::_sendLog(const QString& logFile) //----------------------------------------------------------------------------- bool -MavlinkLogManager::_processUploadResponse(int http_code, QByteArray &data) +MavlinkLogManager::_processUploadResponse(int http_code, QByteArray& data) { qCDebug(MavlinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data); emit readyRead(data); @@ -343,7 +395,7 @@ MavlinkLogManager::_processUploadResponse(int http_code, QByteArray &data) void MavlinkLogManager::_dataAvailable() { - QNetworkReply *reply = qobject_cast(sender()); + QNetworkReply* reply = qobject_cast(sender()); if(!reply) { return; } @@ -355,7 +407,7 @@ MavlinkLogManager::_dataAvailable() void MavlinkLogManager::_uploadFinished() { - QNetworkReply *reply = qobject_cast(sender()); + QNetworkReply* reply = qobject_cast(sender()); if(!reply) { return; } @@ -379,8 +431,9 @@ MavlinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal) { if(bytesTotal) { qreal progress = (qreal)bytesSent / (qreal)bytesTotal; - if(_currentLogfile) + if(_currentLogfile) { _currentLogfile->setProgress(progress); + } } qCDebug(MavlinkLogManagerLog) << bytesSent << "of" << bytesTotal; } @@ -393,17 +446,15 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* 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. - // Disconnect the previous one (if any) - if (_vehicle) { + if(_vehicle) { disconnect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); _vehicle = NULL; emit canStartLogChanged(); } // Connect new system - if (vehicle) - { + if(vehicle) { _vehicle = vehicle; connect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); connect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); @@ -413,10 +464,47 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* vehicle) //----------------------------------------------------------------------------- void -MavlinkLogManager::_mavlinkLogData(Vehicle * /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) +MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) +{ + if(_currentSavingFileFd) { + if(sequence != _sequence) { + qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; + if(first_message < 255) { + data += first_message; + length -= first_message; + } else { + return; + } + } + if(fwrite(data, 1, length, _currentSavingFileFd) != (size_t)length) { + fclose(_currentSavingFileFd); + _currentSavingFileFd = NULL; + qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFileStr; + _logRunning = false; + _vehicle->stopMavlinkLog(); + emit logRunningChanged(); + } + } else { + qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected."; + } + _sequence = sequence + 1; +} + +//----------------------------------------------------------------------------- +bool +MavlinkLogManager::_createNewLog() { - Q_UNUSED(data); - qDebug() << "Mavlink Log:" << sequence << length << first_message; + if(_currentSavingFileFd) { + fclose(_currentSavingFileFd); + } + _currentSavingFileStr.sprintf("%s/%03d-%s.ulg", _logPath.toLatin1().data(), _vehicle->id(), QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); + _currentSavingFileFd = fopen(_currentSavingFileStr.toLatin1().data(), "wb"); + if(!_currentSavingFileFd) { + qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFileStr; + _currentSavingFileStr.clear(); + } + _sequence = 0; + return _currentSavingFileFd != NULL; } //----------------------------------------------------------------------------- @@ -426,17 +514,11 @@ MavlinkLogManager::_armedChanged(bool armed) if(_vehicle) { if(armed) { if(_enableAutoStart) { - _vehicle->startMavlinkLog(); - _logRunning = true; - emit logRunningChanged(); + startLogging(); } } else { if(_logRunning && _enableAutoStart) { - _vehicle->stopMavlinkLog(); - emit logRunningChanged(); - if(_enableAutoUpload) { - //-- TODO: Queue log for auto upload - } + stopLogging(); } } } diff --git a/src/uas/MavlinkLogManager.h b/src/uas/MavlinkLogManager.h index 3ef3dc5..7f5cf85 100644 --- a/src/uas/MavlinkLogManager.h +++ b/src/uas/MavlinkLogManager.h @@ -131,6 +131,8 @@ private slots: private: bool _sendLog (const QString& logFile); bool _processUploadResponse (int http_code, QByteArray &data); + bool _createNewLog (); + int _getFirstSelected (); private: QString _description; @@ -144,6 +146,10 @@ private: MavlinkLogFiles* _currentLogfile; Vehicle* _vehicle; bool _logRunning; + bool _loggingDisabled; + FILE* _currentSavingFileFd; + QString _currentSavingFileStr; + uint16_t _sequence; }; #endif diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index b73fc56..c5b34c9 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -37,12 +37,10 @@ Rectangle { var selected = 0 for(var i = 0; i < QGroundControl.mavlinkLogManager.logFiles.count; i++) { var logFile = QGroundControl.mavlinkLogManager.logFiles.get(i) - console.log(logFile.selected) if(logFile.selected) selected++ } _selectedCount = selected - console.log(_selectedCount) } } @@ -296,26 +294,27 @@ Rectangle { id: logFilesColumn spacing: ScreenTools.defaultFontPixelWidth anchors.centerIn: parent + width: ScreenTools.defaultFontPixelWidth * 68 Rectangle { - width: ScreenTools.defaultFontPixelWidth * 52 + width: ScreenTools.defaultFontPixelWidth * 64 height: ScreenTools.defaultFontPixelHeight * 10 anchors.horizontalCenter: parent.horizontalCenter - color: qgcPal.windowShade + color: qgcPal.window border.color: qgcPal.text border.width: 0.5 ListView { - width: ScreenTools.defaultFontPixelWidth * 50 - height: ScreenTools.defaultFontPixelHeight * 9 + width: ScreenTools.defaultFontPixelWidth * 56 + height: ScreenTools.defaultFontPixelHeight * 8.75 anchors.centerIn: parent orientation: ListView.Vertical model: QGroundControl.mavlinkLogManager.logFiles + clip: true delegate: Rectangle { - width: ScreenTools.defaultFontPixelWidth * 48 + width: ScreenTools.defaultFontPixelWidth * 52 height: ScreenTools.defaultFontPixelHeight * 1.25 - color: index % 2 == 0 ? qgcPal.window : qgcPal.windowShade - anchors.horizontalCenter: parent.horizontalCenter; + color: qgcPal.window Row { - width: ScreenTools.defaultFontPixelWidth * 46 + width: ScreenTools.defaultFontPixelWidth * 50 anchors.centerIn: parent spacing: ScreenTools.defaultFontPixelWidth QGCCheckBox { @@ -327,10 +326,10 @@ Rectangle { } QGCLabel { text: object.name - width: ScreenTools.defaultFontPixelWidth * 20 + width: ScreenTools.defaultFontPixelWidth * 28 } QGCLabel { - text: object.size + text: Number(object.size).toLocaleString(Qt.locale(), 'f', 0) visible: !object.uploading width: ScreenTools.defaultFontPixelWidth * 20; horizontalAlignment: Text.AlignRight From c615f477c76b67dbbfeee83da2487b06c4a91afe Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Tue, 25 Oct 2016 17:09:28 -0400 Subject: [PATCH 04/10] Finished. Needs testing and debugging. --- src/uas/MavlinkLogManager.cc | 172 +++++++++++++++++++++++++-------- src/uas/MavlinkLogManager.h | 53 ++++++++-- src/ui/preferences/MavlinkSettings.qml | 27 ++++-- 3 files changed, 197 insertions(+), 55 deletions(-) diff --git a/src/uas/MavlinkLogManager.cc b/src/uas/MavlinkLogManager.cc index 17b1e8e..655dd37 100644 --- a/src/uas/MavlinkLogManager.cc +++ b/src/uas/MavlinkLogManager.cc @@ -28,6 +28,7 @@ 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"; //----------------------------------------------------------------------------- MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& filePath) @@ -44,6 +45,14 @@ MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& file //----------------------------------------------------------------------------- void +MavlinkLogFiles::setSize(quint32 size) +{ + _size = size; + emit sizeChanged(); +} + +//----------------------------------------------------------------------------- +void MavlinkLogFiles::setSelected(bool selected) { _selected = selected; @@ -68,6 +77,14 @@ MavlinkLogFiles::setProgress(qreal progress) } //----------------------------------------------------------------------------- +void +MavlinkLogFiles::setWriting(bool writing) +{ + _writing = writing; + emit writingChanged(); +} + +//----------------------------------------------------------------------------- MavlinkLogManager::MavlinkLogManager(QGCApplication* app) : QGCTool(app) , _enableAutoUpload(true) @@ -77,8 +94,9 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) , _vehicle(NULL) , _logRunning(false) , _loggingDisabled(false) - , _currentSavingFileFd(NULL) + , _currentSavingFile(NULL) , _sequence(0) + , _deleteAfterUpload(false) { //-- Get saved settings QSettings settings; @@ -87,6 +105,7 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) 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"; @@ -100,7 +119,7 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) //-- Load current list of logs QDirIterator it(_logPath, QStringList() << "*.ulg", QDir::Files); while(it.hasNext()) { - _logFiles.append(new MavlinkLogFiles(this, it.next())); + _insertNewLog(new MavlinkLogFiles(this, it.next())); } qCDebug(MavlinkLogManagerLog) << "Mavlink logs directory:" << _logPath; } @@ -183,8 +202,18 @@ MavlinkLogManager::setEnableAutoStart(bool enable) } //----------------------------------------------------------------------------- +void +MavlinkLogManager::setDeleteAfterUpload(bool enable) +{ + _deleteAfterUpload = enable; + QSettings settings; + settings.setValue(kEnableDeletetKey, enable); + emit deleteAfterUploadChanged(); +} + +//----------------------------------------------------------------------------- bool -MavlinkLogManager::busy() +MavlinkLogManager::uploading() { return _currentLogfile != NULL; } @@ -208,12 +237,32 @@ MavlinkLogManager::uploadLog() filePath += _currentLogfile->name(); filePath += ".ulg"; _sendLog(filePath); - emit busyChanged(); + emit uploadingChanged(); return; } } _currentLogfile = NULL; - emit busyChanged(); + 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(_logFiles.get(i)); + if(newLog->name() < f->name()) { + _logFiles.insert(i, newLog); + return; + } + } + _logFiles.append(newLog); + } } //----------------------------------------------------------------------------- @@ -239,23 +288,30 @@ MavlinkLogManager::deleteLog() if(idx < 0) { break; } - MavlinkLogFiles* f = qobject_cast(_logFiles.get(idx)); - QString filePath = _logPath; - filePath += "/"; - filePath += f->name(); - filePath += ".ulg"; - QFile gone(filePath); - if(!gone.remove()) { - qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath; - } - _logFiles.removeAt(idx); - delete f; - emit logFilesChanged(); + MavlinkLogFiles* log = qobject_cast(_logFiles.get(idx)); + _deleteLog(log); } } //----------------------------------------------------------------------------- void +MavlinkLogManager::_deleteLog(MavlinkLogFiles* log) +{ + QString filePath = _logPath; + filePath += "/"; + filePath += log->name(); + filePath += ".ulg"; + QFile gone(filePath); + if(!gone.remove()) { + qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath; + } + _logFiles.removeOne(log); + delete log; + emit logFilesChanged(); +} + +//----------------------------------------------------------------------------- +void MavlinkLogManager::cancelUpload() { for(int i = 0; i < _logFiles.count(); i++) { @@ -288,19 +344,24 @@ void MavlinkLogManager::stopLogging() { if(_vehicle) { + //-- Tell vehicle to stop sending logs _vehicle->stopMavlinkLog(); - _logRunning = false; - emit logRunningChanged(); - if(_currentSavingFileFd) { - fclose(_currentSavingFileFd); - _logFiles.append(new MavlinkLogFiles(this, _currentSavingFileStr)); - emit logFilesChanged(); - _currentSavingFileFd = NULL; - _currentSavingFileStr.clear(); - //-- TODO: If auto upload is set, schedule it - if(_enableAutoUpload) { - //-- Queue log for auto upload + if(_currentSavingFile) { + _currentSavingFile->close(); + if(_currentSavingFile->record) { + _currentSavingFile->record->setWriting(false); + if(_enableAutoUpload) { + //-- Queue log for auto upload (set selected flag) + _currentSavingFile->record->setSelected(true); + if(!uploading()) { + uploadLog(); + } + } } + delete _currentSavingFile; + _currentSavingFile = NULL; + _logRunning = false; + emit logRunningChanged(); } } } @@ -416,8 +477,14 @@ MavlinkLogManager::_uploadFinished() if(_processUploadResponse(http_code, data)) { qCDebug(MavlinkLogManagerLog) << "Log uploaded."; emit succeed(); + if(_deleteAfterUpload) { + if(_currentLogfile) { + _deleteLog(_currentLogfile); + _currentLogfile = NULL; + } + } } else { - qCDebug(MavlinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); + qCWarning(MavlinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); emit failed(); } reply->deleteLater(); @@ -466,7 +533,7 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* vehicle) void MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) { - if(_currentSavingFileFd) { + if(_currentSavingFile) { if(sequence != _sequence) { qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; if(first_message < 255) { @@ -476,17 +543,26 @@ MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system return; } } - if(fwrite(data, 1, length, _currentSavingFileFd) != (size_t)length) { - fclose(_currentSavingFileFd); - _currentSavingFileFd = NULL; - qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFileStr; + if(fwrite(data, 1, length, _currentSavingFile->fd) != (size_t)length) { + qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFile->fileName; + delete _currentSavingFile; + _currentSavingFile = NULL; _logRunning = false; _vehicle->stopMavlinkLog(); emit logRunningChanged(); } } else { + length = 0; qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected."; } + //-- Update file size + if(_currentSavingFile) { + _currentSavingFile->close(); + if(_currentSavingFile->record) { + quint32 size = _currentSavingFile->record->size() + length; + _currentSavingFile->record->setSize(size); + } + } _sequence = sequence + 1; } @@ -494,17 +570,29 @@ MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system bool MavlinkLogManager::_createNewLog() { - if(_currentSavingFileFd) { - fclose(_currentSavingFileFd); + if(_currentSavingFile) { + delete _currentSavingFile; + _currentSavingFile = NULL; } - _currentSavingFileStr.sprintf("%s/%03d-%s.ulg", _logPath.toLatin1().data(), _vehicle->id(), QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); - _currentSavingFileFd = fopen(_currentSavingFileStr.toLatin1().data(), "wb"); - if(!_currentSavingFileFd) { - qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFileStr; - _currentSavingFileStr.clear(); + _currentSavingFile = new CurrentRunningLog; + _currentSavingFile->fileName.sprintf("%s/%03d-%s.ulg", + _logPath.toLatin1().data(), + _vehicle->id(), + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); + _currentSavingFile->fd = fopen(_currentSavingFile->fileName.toLatin1().data(), "wb"); + if(_currentSavingFile->fd) { + MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName); + newLog->setWriting(true); + _insertNewLog(newLog); + _currentSavingFile->record = newLog; + emit logFilesChanged(); + } else { + qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFile->fileName; + delete _currentSavingFile; + _currentSavingFile = NULL; } _sequence = 0; - return _currentSavingFileFd != NULL; + return _currentSavingFile != NULL; } //----------------------------------------------------------------------------- diff --git a/src/uas/MavlinkLogManager.h b/src/uas/MavlinkLogManager.h index 7f5cf85..aabe104 100644 --- a/src/uas/MavlinkLogManager.h +++ b/src/uas/MavlinkLogManager.h @@ -31,25 +31,31 @@ public: MavlinkLogFiles (MavlinkLogManager* manager, const QString& filePath); Q_PROPERTY(QString name READ name CONSTANT) - Q_PROPERTY(quint32 size READ size 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) QString name () { return _name; } quint32 size () { return _size; } bool selected () { return _selected; } bool uploading () { return _uploading; } qreal progress () { return _progress; } + bool writing () { return _writing; } void setSelected (bool selected); void setUploading (bool uploading); void setProgress (qreal progress); + void setWriting (bool writing); + void setSize (quint32 size); signals: + void sizeChanged (); void selectedChanged (); void uploadingChanged (); void progressChanged (); + void writingChanged (); private: MavlinkLogManager* _manager; @@ -58,8 +64,37 @@ private: bool _selected; bool _uploading; qreal _progress; + bool _writing; }; +//----------------------------------------------------------------------------- +class CurrentRunningLog +{ +public: + CurrentRunningLog() + : fd(NULL) + , record(NULL) + , written(0) + { + } + ~CurrentRunningLog() + { + close(); + } + void close() + { + if(fd) { + fclose(fd); + fd = NULL; + } + } + FILE* fd; + QString fileName; + MavlinkLogFiles* record; + quint32 written; +}; + +//----------------------------------------------------------------------------- class MavlinkLogManager : public QGCTool { Q_OBJECT @@ -73,7 +108,8 @@ public: 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 busy READ busy NOTIFY busyChanged) + 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) @@ -89,9 +125,10 @@ public: QString uploadURL () { return _uploadURL; } bool enableAutoUpload () { return _enableAutoUpload; } bool enableAutoStart () { return _enableAutoStart; } - bool busy (); + bool uploading (); bool logRunning () { return _logRunning; } bool canStartLog () { return _vehicle != NULL; } + bool deleteAfterUpload () { return _deleteAfterUpload; } QmlObjectListModel* logFiles () { return &_logFiles; } @@ -100,6 +137,7 @@ public: void setUploadURL (QString url); void setEnableAutoUpload (bool enable); void setEnableAutoStart (bool enable); + void setDeleteAfterUpload(bool enable); // Override from QGCTool void setToolbox (QGCToolbox *toolbox); @@ -112,13 +150,14 @@ signals: void enableAutoStartChanged (); void logFilesChanged (); void selectedCountChanged (); - void busyChanged (); + void uploadingChanged (); void readyRead (QByteArray data); void failed (); void succeed (); void abortUpload (); void logRunningChanged (); void canStartLogChanged (); + void deleteAfterUploadChanged (); private slots: void _uploadFinished (); @@ -133,6 +172,8 @@ private: bool _processUploadResponse (int http_code, QByteArray &data); bool _createNewLog (); int _getFirstSelected (); + void _insertNewLog (MavlinkLogFiles* newLog); + void _deleteLog (MavlinkLogFiles* log); private: QString _description; @@ -147,9 +188,9 @@ private: Vehicle* _vehicle; bool _logRunning; bool _loggingDisabled; - FILE* _currentSavingFileFd; - QString _currentSavingFileStr; + CurrentRunningLog* _currentSavingFile; uint16_t _sequence; + bool _deleteAfterUpload; }; #endif diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index c5b34c9..f7ea010 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -262,6 +262,7 @@ Rectangle { //----------------------------------------------------------------- //-- Automatic Upload QGCCheckBox { + id: autoUploadCheck text: qsTr("Enable automatic log uploads") checked: QGroundControl.mavlinkLogManager.enableAutoUpload enabled: emailField.text !== "" && urlField !== "" @@ -269,6 +270,16 @@ Rectangle { QGroundControl.mavlinkLogManager.enableAutoUpload = checked } } + //----------------------------------------------------------------- + //-- Delete log after upload + QGCCheckBox { + text: qsTr("Delete log file after uploading") + checked: QGroundControl.mavlinkLogManager.deleteAfterUpload + enabled: emailField.text !== "" && urlField !== "" && autoUploadCheck.checked + onClicked: { + QGroundControl.mavlinkLogManager.deleteAfterUpload = checked + } + } } } //----------------------------------------------------------------- @@ -327,11 +338,13 @@ Rectangle { QGCLabel { text: object.name width: ScreenTools.defaultFontPixelWidth * 28 + color: object.writing ? qgcPal.warningText : qgcPal.text } QGCLabel { text: Number(object.size).toLocaleString(Qt.locale(), 'f', 0) visible: !object.uploading width: ScreenTools.defaultFontPixelWidth * 20; + color: object.writing ? qgcPal.warningText : qgcPal.text horizontalAlignment: Text.AlignRight } ProgressBar { @@ -352,7 +365,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter QGCButton { text: "Check All" - enabled: !QGroundControl.mavlinkLogManager.busy + 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) @@ -362,7 +375,7 @@ Rectangle { } QGCButton { text: "Check None" - enabled: !QGroundControl.mavlinkLogManager.busy + 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) @@ -372,7 +385,7 @@ Rectangle { } QGCButton { text: "Delete Selected" - enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.busy + enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning onClicked: deleteDialog.open() MessageDialog { id: deleteDialog @@ -388,8 +401,8 @@ Rectangle { } QGCButton { text: "Upload Selected" - enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.busy - visible: !QGroundControl.mavlinkLogManager.busy + enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning + visible: !QGroundControl.mavlinkLogManager.uploading onClicked: { QGroundControl.mavlinkLogManager.emailAddress = emailField.text if(QGroundControl.mavlinkLogManager.emailAddress === "") @@ -411,8 +424,8 @@ Rectangle { } QGCButton { text: "Cancel" - enabled: QGroundControl.mavlinkLogManager.busy - visible: QGroundControl.mavlinkLogManager.busy + enabled: QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning + visible: QGroundControl.mavlinkLogManager.uploading onClicked: cancelDialog.open() MessageDialog { id: cancelDialog From dc2be3bff251b9199e38ed1bfb8244eb3c206e79 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Tue, 25 Oct 2016 17:30:01 -0400 Subject: [PATCH 05/10] Bug fixes. Moving source files into Vehicles as it is a more coherent place. --- qgroundcontrol.pro | 4 +- src/Vehicle/MavlinkLogManager.cc | 624 +++++++++++++++++++++++++++++++++ src/Vehicle/MavlinkLogManager.h | 190 ++++++++++ src/uas/MavlinkLogManager.cc | 613 -------------------------------- src/uas/MavlinkLogManager.h | 196 ----------- src/ui/preferences/MavlinkSettings.qml | 1 + 6 files changed, 817 insertions(+), 811 deletions(-) create mode 100644 src/Vehicle/MavlinkLogManager.cc create mode 100644 src/Vehicle/MavlinkLogManager.h delete mode 100644 src/uas/MavlinkLogManager.cc delete mode 100644 src/uas/MavlinkLogManager.h diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index c8adcb5..a070e4a 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -334,7 +334,7 @@ HEADERS += \ src/uas/UAS.h \ src/uas/UASInterface.h \ src/uas/UASMessageHandler.h \ - src/uas/MavlinkLogManager.h \ + src/Vehicle/MavlinkLogManager.h \ src/ui/toolbar/MainToolBarController.h \ src/AutoPilotPlugins/PX4/PX4AirframeLoader.h \ src/AutoPilotPlugins/APM/APMAirframeLoader.h \ @@ -499,7 +499,7 @@ SOURCES += \ src/QmlControls/QmlObjectListModel.cc \ src/uas/UAS.cc \ src/uas/UASMessageHandler.cc \ - src/uas/MavlinkLogManager.cc \ + src/Vehicle/MavlinkLogManager.cc \ src/ui/toolbar/MainToolBarController.cc \ src/AutoPilotPlugins/PX4/PX4AirframeLoader.cc \ src/AutoPilotPlugins/APM/APMAirframeLoader.cc \ diff --git a/src/Vehicle/MavlinkLogManager.cc b/src/Vehicle/MavlinkLogManager.cc new file mode 100644 index 0000000..56270fb --- /dev/null +++ b/src/Vehicle/MavlinkLogManager.cc @@ -0,0 +1,624 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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"; + +//----------------------------------------------------------------------------- +MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& filePath) + : _manager(manager) + , _size(0) + , _selected(false) + , _uploading(false) + , _progress(0) + , _writing(false) +{ + QFileInfo fi(filePath); + _name = fi.baseName(); + _size = (quint32)fi.size(); +} + +//----------------------------------------------------------------------------- +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 CurrentRunningLog::close() +{ + if(fd) { + fclose(fd); + fd = NULL; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +MavlinkLogManager::MavlinkLogManager(QGCApplication* app) + : QGCTool(app) + , _enableAutoUpload(true) + , _enableAutoStart(true) + , _nam(NULL) + , _currentLogfile(NULL) + , _vehicle(NULL) + , _logRunning(false) + , _loggingDisabled(false) + , _currentSavingFile(NULL) + , _sequence(0) + , _deleteAfterUpload(false) +{ + //-- 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 + QDirIterator it(_logPath, QStringList() << "*.ulg", 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("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); + if(!_loggingDisabled) { + connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); + } + // _uploadURL = "http://192.168.1.21/px4"; + // _uploadURL = "http://192.168.1.9:8080"; + // _emailAddress = "gus.grubba.com"; + // _description = "Test from QGroundControl - Discard"; + // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); +} + +//----------------------------------------------------------------------------- +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(_logFiles.get(i)); + Q_ASSERT(_currentLogfile); + if(_currentLogfile->selected()) { + _currentLogfile->setSelected(false); + _currentLogfile->setUploading(true); + _currentLogfile->setProgress(0.0); + QString filePath = _logPath; + filePath += "/"; + filePath += _currentLogfile->name(); + filePath += ".ulg"; + _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(_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(_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(_logFiles.get(idx)); + _deleteLog(log); + } +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_deleteLog(MavlinkLogFiles* log) +{ + QString filePath = _logPath; + filePath += "/"; + filePath += log->name(); + filePath += ".ulg"; + QFile gone(filePath); + if(!gone.remove()) { + qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath; + } + _logFiles.removeOne(log); + delete log; + emit logFilesChanged(); +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::cancelUpload() +{ + for(int i = 0; i < _logFiles.count(); i++) { + MavlinkLogFiles* pLogFile = qobject_cast(_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; + emit logRunningChanged(); + } + } +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::stopLogging() +{ + if(_vehicle) { + //-- Tell vehicle to stop sending logs + _vehicle->stopMavlinkLog(); + if(_currentSavingFile) { + _currentSavingFile->close(); + if(_currentSavingFile->record) { + _currentSavingFile->record->setWriting(false); + if(_enableAutoUpload) { + //-- Queue log for auto upload (set selected flag) + _currentSavingFile->record->setSelected(true); + if(!uploading()) { + uploadLog(); + } + } + } + delete _currentSavingFile; + _currentSavingFile = NULL; + _logRunning = false; + 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); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + 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(sender()); + if(!reply) { + return; + } + QByteArray data = reply->readAll(); + qCDebug(MavlinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data); +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_uploadFinished() +{ + QNetworkReply* reply = qobject_cast(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 { + 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. + // Disconnect the previous one (if any) + if(_vehicle) { + disconnect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); + disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); + _vehicle = NULL; + emit canStartLogChanged(); + } + // Connect new system + if(vehicle) { + _vehicle = vehicle; + connect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); + connect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); + emit canStartLogChanged(); + } +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) +{ + if(_currentSavingFile && _currentSavingFile->fd) { + if(sequence != _sequence) { + qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; + if(first_message < 255) { + data += first_message; + length -= first_message; + } else { + return; + } + } + if(fwrite(data, 1, length, _currentSavingFile->fd) != (size_t)length) { + qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFile->fileName; + delete _currentSavingFile; + _currentSavingFile = NULL; + _logRunning = false; + _vehicle->stopMavlinkLog(); + emit logRunningChanged(); + } + } else { + length = 0; + qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected."; + } + //-- Update file size + if(_currentSavingFile) { + if(_currentSavingFile->record) { + quint32 size = _currentSavingFile->record->size() + length; + _currentSavingFile->record->setSize(size); + } + } + _sequence = sequence + 1; +} + +//----------------------------------------------------------------------------- +bool +MavlinkLogManager::_createNewLog() +{ + if(_currentSavingFile) { + delete _currentSavingFile; + _currentSavingFile = NULL; + } + _currentSavingFile = new CurrentRunningLog; + _currentSavingFile->fileName.sprintf("%s/%03d-%s.ulg", + _logPath.toLatin1().data(), + _vehicle->id(), + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); + _currentSavingFile->fd = fopen(_currentSavingFile->fileName.toLatin1().data(), "wb"); + if(_currentSavingFile->fd) { + MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName); + newLog->setWriting(true); + _insertNewLog(newLog); + _currentSavingFile->record = newLog; + emit logFilesChanged(); + } else { + qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFile->fileName; + delete _currentSavingFile; + _currentSavingFile = NULL; + } + _sequence = 0; + return _currentSavingFile != NULL; +} + +//----------------------------------------------------------------------------- +void +MavlinkLogManager::_armedChanged(bool armed) +{ + if(_vehicle) { + if(armed) { + if(_enableAutoStart) { + startLogging(); + } + } else { + if(_logRunning && _enableAutoStart) { + stopLogging(); + } + } + } +} diff --git a/src/Vehicle/MavlinkLogManager.h b/src/Vehicle/MavlinkLogManager.h new file mode 100644 index 0000000..701c69e --- /dev/null +++ b/src/Vehicle/MavlinkLogManager.h @@ -0,0 +1,190 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * 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 + +#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); + + 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) + + QString name () { return _name; } + quint32 size () { return _size; } + bool selected () { return _selected; } + bool uploading () { return _uploading; } + qreal progress () { return _progress; } + bool writing () { return _writing; } + + void setSelected (bool selected); + void setUploading (bool uploading); + void setProgress (qreal progress); + void setWriting (bool writing); + void setSize (quint32 size); + +signals: + void sizeChanged (); + void selectedChanged (); + void uploadingChanged (); + void progressChanged (); + void writingChanged (); + +private: + MavlinkLogManager* _manager; + QString _name; + quint32 _size; + bool _selected; + bool _uploading; + qreal _progress; + bool _writing; +}; + +//----------------------------------------------------------------------------- +class CurrentRunningLog +{ +public: + CurrentRunningLog() + : fd(NULL) + , record(NULL) + , written(0) + { + } + ~CurrentRunningLog() + { + close(); + } + void close(); + FILE* fd; + QString fileName; + MavlinkLogFiles* record; + quint32 written; +}; + +//----------------------------------------------------------------------------- +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 length, uint8_t first_message, const uint8_t* data, bool acked); + void _armedChanged (bool armed); + +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); + +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; + CurrentRunningLog* _currentSavingFile; + uint16_t _sequence; + bool _deleteAfterUpload; +}; + +#endif diff --git a/src/uas/MavlinkLogManager.cc b/src/uas/MavlinkLogManager.cc deleted file mode 100644 index 655dd37..0000000 --- a/src/uas/MavlinkLogManager.cc +++ /dev/null @@ -1,613 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2016 QGROUNDCONTROL PROJECT - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -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"; - -//----------------------------------------------------------------------------- -MavlinkLogFiles::MavlinkLogFiles(MavlinkLogManager* manager, const QString& filePath) - : _manager(manager) - , _size(0) - , _selected(false) - , _uploading(false) - , _progress(0) -{ - QFileInfo fi(filePath); - _name = fi.baseName(); - _size = (quint32)fi.size(); -} - -//----------------------------------------------------------------------------- -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(); -} - -//----------------------------------------------------------------------------- -MavlinkLogManager::MavlinkLogManager(QGCApplication* app) - : QGCTool(app) - , _enableAutoUpload(true) - , _enableAutoStart(true) - , _nam(NULL) - , _currentLogfile(NULL) - , _vehicle(NULL) - , _logRunning(false) - , _loggingDisabled(false) - , _currentSavingFile(NULL) - , _sequence(0) - , _deleteAfterUpload(false) -{ - //-- 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 - QDirIterator it(_logPath, QStringList() << "*.ulg", 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("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); - if(!_loggingDisabled) { - connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); - } - // _uploadURL = "http://192.168.1.21/px4"; - // _uploadURL = "http://192.168.1.9:8080"; - // _emailAddress = "gus.grubba.com"; - // _description = "Test from QGroundControl - Discard"; - // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); -} - -//----------------------------------------------------------------------------- -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(_logFiles.get(i)); - Q_ASSERT(_currentLogfile); - if(_currentLogfile->selected()) { - _currentLogfile->setSelected(false); - _currentLogfile->setUploading(true); - _currentLogfile->setProgress(0.0); - QString filePath = _logPath; - filePath += "/"; - filePath += _currentLogfile->name(); - filePath += ".ulg"; - _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(_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(_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(_logFiles.get(idx)); - _deleteLog(log); - } -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::_deleteLog(MavlinkLogFiles* log) -{ - QString filePath = _logPath; - filePath += "/"; - filePath += log->name(); - filePath += ".ulg"; - QFile gone(filePath); - if(!gone.remove()) { - qCWarning(MavlinkLogManagerLog) << "Could not delete Mavlink log file:" << _logPath; - } - _logFiles.removeOne(log); - delete log; - emit logFilesChanged(); -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::cancelUpload() -{ - for(int i = 0; i < _logFiles.count(); i++) { - MavlinkLogFiles* pLogFile = qobject_cast(_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; - emit logRunningChanged(); - } - } -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::stopLogging() -{ - if(_vehicle) { - //-- Tell vehicle to stop sending logs - _vehicle->stopMavlinkLog(); - if(_currentSavingFile) { - _currentSavingFile->close(); - if(_currentSavingFile->record) { - _currentSavingFile->record->setWriting(false); - if(_enableAutoUpload) { - //-- Queue log for auto upload (set selected flag) - _currentSavingFile->record->setSelected(true); - if(!uploading()) { - uploadLog(); - } - } - } - delete _currentSavingFile; - _currentSavingFile = NULL; - _logRunning = false; - 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); - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - 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(sender()); - if(!reply) { - return; - } - QByteArray data = reply->readAll(); - qCDebug(MavlinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data); -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::_uploadFinished() -{ - QNetworkReply* reply = qobject_cast(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 { - 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. - // Disconnect the previous one (if any) - if(_vehicle) { - disconnect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); - disconnect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); - _vehicle = NULL; - emit canStartLogChanged(); - } - // Connect new system - if(vehicle) { - _vehicle = vehicle; - connect(_vehicle, &Vehicle::armedChanged, this, &MavlinkLogManager::_armedChanged); - connect(_vehicle, &Vehicle::mavlinkLogData, this, &MavlinkLogManager::_mavlinkLogData); - emit canStartLogChanged(); - } -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) -{ - if(_currentSavingFile) { - if(sequence != _sequence) { - qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; - if(first_message < 255) { - data += first_message; - length -= first_message; - } else { - return; - } - } - if(fwrite(data, 1, length, _currentSavingFile->fd) != (size_t)length) { - qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFile->fileName; - delete _currentSavingFile; - _currentSavingFile = NULL; - _logRunning = false; - _vehicle->stopMavlinkLog(); - emit logRunningChanged(); - } - } else { - length = 0; - qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected."; - } - //-- Update file size - if(_currentSavingFile) { - _currentSavingFile->close(); - if(_currentSavingFile->record) { - quint32 size = _currentSavingFile->record->size() + length; - _currentSavingFile->record->setSize(size); - } - } - _sequence = sequence + 1; -} - -//----------------------------------------------------------------------------- -bool -MavlinkLogManager::_createNewLog() -{ - if(_currentSavingFile) { - delete _currentSavingFile; - _currentSavingFile = NULL; - } - _currentSavingFile = new CurrentRunningLog; - _currentSavingFile->fileName.sprintf("%s/%03d-%s.ulg", - _logPath.toLatin1().data(), - _vehicle->id(), - QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); - _currentSavingFile->fd = fopen(_currentSavingFile->fileName.toLatin1().data(), "wb"); - if(_currentSavingFile->fd) { - MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName); - newLog->setWriting(true); - _insertNewLog(newLog); - _currentSavingFile->record = newLog; - emit logFilesChanged(); - } else { - qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFile->fileName; - delete _currentSavingFile; - _currentSavingFile = NULL; - } - _sequence = 0; - return _currentSavingFile != NULL; -} - -//----------------------------------------------------------------------------- -void -MavlinkLogManager::_armedChanged(bool armed) -{ - if(_vehicle) { - if(armed) { - if(_enableAutoStart) { - startLogging(); - } - } else { - if(_logRunning && _enableAutoStart) { - stopLogging(); - } - } - } -} diff --git a/src/uas/MavlinkLogManager.h b/src/uas/MavlinkLogManager.h deleted file mode 100644 index aabe104..0000000 --- a/src/uas/MavlinkLogManager.h +++ /dev/null @@ -1,196 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2016 QGROUNDCONTROL PROJECT - * - * 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 - -#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); - - 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) - - QString name () { return _name; } - quint32 size () { return _size; } - bool selected () { return _selected; } - bool uploading () { return _uploading; } - qreal progress () { return _progress; } - bool writing () { return _writing; } - - void setSelected (bool selected); - void setUploading (bool uploading); - void setProgress (qreal progress); - void setWriting (bool writing); - void setSize (quint32 size); - -signals: - void sizeChanged (); - void selectedChanged (); - void uploadingChanged (); - void progressChanged (); - void writingChanged (); - -private: - MavlinkLogManager* _manager; - QString _name; - quint32 _size; - bool _selected; - bool _uploading; - qreal _progress; - bool _writing; -}; - -//----------------------------------------------------------------------------- -class CurrentRunningLog -{ -public: - CurrentRunningLog() - : fd(NULL) - , record(NULL) - , written(0) - { - } - ~CurrentRunningLog() - { - close(); - } - void close() - { - if(fd) { - fclose(fd); - fd = NULL; - } - } - FILE* fd; - QString fileName; - MavlinkLogFiles* record; - quint32 written; -}; - -//----------------------------------------------------------------------------- -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 length, uint8_t first_message, const uint8_t* data, bool acked); - void _armedChanged (bool armed); - -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); - -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; - CurrentRunningLog* _currentSavingFile; - uint16_t _sequence; - bool _deleteAfterUpload; -}; - -#endif diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index f7ea010..d0b2426 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -331,6 +331,7 @@ Rectangle { QGCCheckBox { width: ScreenTools.defaultFontPixelWidth * 4 checked: object.selected + enabled: !object.writing && !object.uploading onClicked: { object.selected = checked } From a85cdd5946f6e8e4330e19ffb6d159c757688614 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Tue, 25 Oct 2016 22:13:21 -0400 Subject: [PATCH 06/10] Final UI tweaks and fixes. --- src/Vehicle/MavlinkLogManager.cc | 91 ++++++++++++++++++++++++---------- src/Vehicle/MavlinkLogManager.h | 10 +++- src/ui/preferences/MavlinkSettings.qml | 71 +++++++++++++++++--------- 3 files changed, 121 insertions(+), 51 deletions(-) diff --git a/src/Vehicle/MavlinkLogManager.cc b/src/Vehicle/MavlinkLogManager.cc index 56270fb..bc9e14d 100644 --- a/src/Vehicle/MavlinkLogManager.cc +++ b/src/Vehicle/MavlinkLogManager.cc @@ -29,19 +29,28 @@ 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) +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(); - _size = (quint32)fi.size(); + if(!newFile) { + _size = (quint32)fi.size(); + QString sideCar = filePath; + sideCar.replace(kUlogExtension, kSidecarExtension); + QFileInfo sc(sideCar); + _uploaded = sc.exists(); + } } //----------------------------------------------------------------------------- @@ -86,6 +95,14 @@ MavlinkLogFiles::setWriting(bool writing) } //----------------------------------------------------------------------------- +void +MavlinkLogFiles::setUploaded(bool uploaded) +{ + _uploaded = uploaded; + emit uploadedChanged(); +} + +//----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CurrentRunningLog::close() { @@ -122,14 +139,16 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) _logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); _logPath += "/MavlinkLogs"; if(!QDir(_logPath).exists()) { - if(QDir().mkpath(_logPath)) { + if(!QDir().mkpath(_logPath)) { qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log download path:" << _logPath; _loggingDisabled = true; } } if(!_loggingDisabled) { //-- Load current list of logs - QDirIterator it(_logPath, QStringList() << "*.ulg", QDir::Files); + QString filter = "*"; + filter += kUlogExtension; + QDirIterator it(_logPath, QStringList() << filter, QDir::Files); while(it.hasNext()) { _insertNewLog(new MavlinkLogFiles(this, it.next())); } @@ -153,11 +172,6 @@ MavlinkLogManager::setToolbox(QGCToolbox* toolbox) if(!_loggingDisabled) { connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); } - // _uploadURL = "http://192.168.1.21/px4"; - // _uploadURL = "http://192.168.1.9:8080"; - // _emailAddress = "gus.grubba.com"; - // _description = "Test from QGroundControl - Discard"; - // _sendLog("/Users/gus/github/work/logs/simulator.ulg"); } //----------------------------------------------------------------------------- @@ -242,15 +256,14 @@ MavlinkLogManager::uploadLog() Q_ASSERT(_currentLogfile); if(_currentLogfile->selected()) { _currentLogfile->setSelected(false); - _currentLogfile->setUploading(true); - _currentLogfile->setProgress(0.0); - QString filePath = _logPath; - filePath += "/"; - filePath += _currentLogfile->name(); - filePath += ".ulg"; - _sendLog(filePath); - emit uploadingChanged(); - return; + 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; @@ -309,14 +322,18 @@ MavlinkLogManager::deleteLog() void MavlinkLogManager::_deleteLog(MavlinkLogFiles* log) { - QString filePath = _logPath; - filePath += "/"; - filePath += log->name(); - filePath += ".ulg"; + 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(); @@ -494,6 +511,17 @@ MavlinkLogManager::_uploadFinished() _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()); @@ -525,6 +553,7 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* 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); @@ -586,13 +615,14 @@ MavlinkLogManager::_createNewLog() _currentSavingFile = NULL; } _currentSavingFile = new CurrentRunningLog; - _currentSavingFile->fileName.sprintf("%s/%03d-%s.ulg", + _currentSavingFile->fileName.sprintf("%s/%03d-%s%s", _logPath.toLatin1().data(), _vehicle->id(), - QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data()); + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data(), + kUlogExtension); _currentSavingFile->fd = fopen(_currentSavingFile->fileName.toLatin1().data(), "wb"); if(_currentSavingFile->fd) { - MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName); + MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName, true); newLog->setWriting(true); _insertNewLog(newLog); _currentSavingFile->record = newLog; @@ -622,3 +652,14 @@ MavlinkLogManager::_armedChanged(bool armed) } } } + +//----------------------------------------------------------------------------- +QString +MavlinkLogManager::_makeFilename(const QString& baseName) +{ + QString filePath = _logPath; + filePath += "/"; + filePath += baseName; + filePath += kUlogExtension; + return filePath; +} diff --git a/src/Vehicle/MavlinkLogManager.h b/src/Vehicle/MavlinkLogManager.h index 701c69e..0ea7c8b 100644 --- a/src/Vehicle/MavlinkLogManager.h +++ b/src/Vehicle/MavlinkLogManager.h @@ -28,7 +28,7 @@ class MavlinkLogFiles : public QObject { Q_OBJECT public: - MavlinkLogFiles (MavlinkLogManager* manager, const QString& filePath); + MavlinkLogFiles (MavlinkLogManager* manager, const QString& filePath, bool newFile = false); Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(quint32 size READ size NOTIFY sizeChanged) @@ -36,6 +36,7 @@ public: 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; } @@ -43,12 +44,14 @@ public: 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 (); @@ -56,6 +59,7 @@ signals: void uploadingChanged (); void progressChanged (); void writingChanged (); + void uploadedChanged (); private: MavlinkLogManager* _manager; @@ -65,6 +69,7 @@ private: bool _uploading; qreal _progress; bool _writing; + bool _uploaded; }; //----------------------------------------------------------------------------- @@ -144,7 +149,7 @@ signals: void enableAutoStartChanged (); void logFilesChanged (); void selectedCountChanged (); - void uploadingChanged (); + void uploadingChanged (); void readyRead (QByteArray data); void failed (); void succeed (); @@ -168,6 +173,7 @@ private: int _getFirstSelected (); void _insertNewLog (MavlinkLogFiles* newLog); void _deleteLog (MavlinkLogFiles* log); + QString _makeFilename (const QString& baseName); private: QString _description; diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index d0b2426..836e902 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -28,6 +28,8 @@ Rectangle { property real _labelWidth: ScreenTools.defaultFontPixelWidth * 28 property real _valueWidth: ScreenTools.defaultFontPixelWidth * 24 property int _selectedCount: 0 + property real _columnSpacing: ScreenTools.defaultFontPixelHeight * 0.25 + QGCPalette { id: qgcPal } @@ -59,6 +61,7 @@ Rectangle { anchors.margins: ScreenTools.defaultFontPixelWidth contentHeight: settingsColumn.height contentWidth: settingsColumn.width + flickableDirection: Flickable.VerticalFlick Column { id: settingsColumn @@ -86,7 +89,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter Column { id: gcsColumn - spacing: ScreenTools.defaultFontPixelWidth + spacing: _columnSpacing anchors.centerIn: parent Row { spacing: ScreenTools.defaultFontPixelWidth @@ -148,31 +151,40 @@ Rectangle { Column { id: mavlogColumn width: gcsColumn.width - spacing: ScreenTools.defaultFontPixelWidth + spacing: _columnSpacing anchors.centerIn: parent //----------------------------------------------------------------- - //-- Enable auto log on arming - QGCCheckBox { - text: qsTr("Enable automatic logging start when vehicle is armed") - checked: QGroundControl.mavlinkLogManager.enableAutoStart - onClicked: { - QGroundControl.mavlinkLogManager.enableAutoStart = checked - } - } - //----------------------------------------------------------------- //-- 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: "Start Logging" - enabled: !QGroundControl.mavlinkLogManager.logRunning && QGroundControl.mavlinkLogManager.canStartLog - onClicked: QGroundControl.mavlinkLogManager.startLogging() + 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: "Stop Logging" - enabled: QGroundControl.mavlinkLogManager.logRunning - onClicked: QGroundControl.mavlinkLogManager.stopLogging() + 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 } } } @@ -198,7 +210,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter Column { id: logColumn - spacing: ScreenTools.defaultFontPixelWidth + spacing: _columnSpacing anchors.centerIn: parent //----------------------------------------------------------------- //-- Email address Field @@ -303,35 +315,37 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter Column { id: logFilesColumn - spacing: ScreenTools.defaultFontPixelWidth + spacing: _columnSpacing * 4 anchors.centerIn: parent width: ScreenTools.defaultFontPixelWidth * 68 Rectangle { width: ScreenTools.defaultFontPixelWidth * 64 - height: ScreenTools.defaultFontPixelHeight * 10 + 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 * 8.75 + height: ScreenTools.defaultFontPixelHeight * 12 anchors.centerIn: parent orientation: ListView.Vertical model: QGroundControl.mavlinkLogManager.logFiles clip: true delegate: Rectangle { width: ScreenTools.defaultFontPixelWidth * 52 - height: ScreenTools.defaultFontPixelHeight * 1.25 + 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 } @@ -340,16 +354,25 @@ Rectangle { 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 + 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 + visible: object.uploading && !object.uploaded width: ScreenTools.defaultFontPixelWidth * 20; height: ScreenTools.defaultFontPixelHeight anchors.verticalCenter: parent.verticalCenter From 62bc5a63c4407407bad349db5fd16386b42aaba2 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Tue, 25 Oct 2016 22:55:55 -0400 Subject: [PATCH 07/10] Qt 5.5.x doesn't know how to handle redirects. --- src/Vehicle/MavlinkLogManager.cc | 2 ++ src/ui/preferences/MavlinkSettings.qml | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Vehicle/MavlinkLogManager.cc b/src/Vehicle/MavlinkLogManager.cc index bc9e14d..7da59ae 100644 --- a/src/Vehicle/MavlinkLogManager.cc +++ b/src/Vehicle/MavlinkLogManager.cc @@ -460,7 +460,9 @@ MavlinkLogManager::_sendLog(const QString& logFile) 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); diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index 836e902..3beaa60 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -25,22 +25,28 @@ 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 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) + if(logFile.selected) { selected++ + //-- If an uploaded file is selected, disable "Upload" button + if(logFile.uploaded) { + _uploadedSelected = true + } + } } _selectedCount = selected } @@ -425,7 +431,7 @@ Rectangle { } QGCButton { text: "Upload Selected" - enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning + enabled: _selectedCount > 0 && !QGroundControl.mavlinkLogManager.uploading && !QGroundControl.mavlinkLogManager.logRunning && !_uploadedSelected visible: !QGroundControl.mavlinkLogManager.uploading onClicked: { QGroundControl.mavlinkLogManager.emailAddress = emailField.text From 95f0259e52f6951418d8ca2c9d3a7bb8035214da Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Wed, 26 Oct 2016 12:45:19 -0400 Subject: [PATCH 08/10] Handle COMMAND_ACK for Start/Stop logging. --- src/Vehicle/MavlinkLogManager.cc | 114 ++++++++++++++++++++++++++++++++++----- src/Vehicle/MavlinkLogManager.h | 5 ++ src/Vehicle/Vehicle.cc | 11 ++-- 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/Vehicle/MavlinkLogManager.cc b/src/Vehicle/MavlinkLogManager.cc index 7da59ae..5d6a958 100644 --- a/src/Vehicle/MavlinkLogManager.cc +++ b/src/Vehicle/MavlinkLogManager.cc @@ -19,6 +19,8 @@ #include #include +#define kTimeOutMilliseconds 1000 + QGC_LOGGING_CATEGORY(MavlinkLogManagerLog, "MavlinkLogManagerLog") static const char* kEmailAddressKey = "MavlinkLogEmail"; @@ -126,6 +128,7 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) , _currentSavingFile(NULL) , _sequence(0) , _deleteAfterUpload(false) + , _loggingCmdTryCount(0) { //-- Get saved settings QSettings settings; @@ -171,6 +174,7 @@ MavlinkLogManager::setToolbox(QGCToolbox* toolbox) qmlRegisterUncreatableType("QGroundControl.MavlinkLogManager", 1, 0, "MavlinkLogManager", "Reference only"); if(!_loggingDisabled) { connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MavlinkLogManager::_activeVehicleChanged); + connect(&_ackTimer, &QTimer::timeout, this, &MavlinkLogManager::_processCmdAck); } } @@ -363,6 +367,8 @@ MavlinkLogManager::startLogging() if(_createNewLog()) { _vehicle->startMavlinkLog(); _logRunning = true; + _loggingCmdTryCount = 0; + _ackTimer.start(kTimeOutMilliseconds); emit logRunningChanged(); } } @@ -375,23 +381,28 @@ MavlinkLogManager::stopLogging() if(_vehicle) { //-- Tell vehicle to stop sending logs _vehicle->stopMavlinkLog(); - if(_currentSavingFile) { - _currentSavingFile->close(); - if(_currentSavingFile->record) { - _currentSavingFile->record->setWriting(false); - if(_enableAutoUpload) { - //-- Queue log for auto upload (set selected flag) - _currentSavingFile->record->setSelected(true); - if(!uploading()) { - uploadLog(); - } + } + if(_currentSavingFile) { + _currentSavingFile->close(); + if(_currentSavingFile->record) { + _currentSavingFile->record->setWriting(false); + if(_enableAutoUpload) { + //-- Queue log for auto upload (set selected flag) + _currentSavingFile->record->setSelected(true); + if(!uploading()) { + uploadLog(); } } - delete _currentSavingFile; - _currentSavingFile = NULL; - _logRunning = false; - emit logRunningChanged(); } + delete _currentSavingFile; + _currentSavingFile = NULL; + _logRunning = false; + if(_vehicle) { + //-- Setup a timer to make sure vehicle received the command + _loggingCmdTryCount = 0; + _ackTimer.start(kTimeOutMilliseconds); + } + emit logRunningChanged(); } } @@ -560,7 +571,10 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* vehicle) 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 @@ -568,14 +582,49 @@ MavlinkLogManager::_activeVehicleChanged(Vehicle* 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 length, uint8_t first_message, const uint8_t* data, bool /*acked*/) { + //-- Disable timer if we got a message before an ACK for the start command + if(_logRunning) { + _ackTimer.stop(); + } if(_currentSavingFile && _currentSavingFile->fd) { if(sequence != _sequence) { qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; @@ -609,6 +658,43 @@ MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system } //----------------------------------------------------------------------------- +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(_currentSavingFile) { + _currentSavingFile->close(); + if(_currentSavingFile->record) { + _deleteLog(_currentSavingFile->record); + } + delete _currentSavingFile; + _currentSavingFile = NULL; + } + _logRunning = false; + emit logRunningChanged(); +} + +//----------------------------------------------------------------------------- bool MavlinkLogManager::_createNewLog() { diff --git a/src/Vehicle/MavlinkLogManager.h b/src/Vehicle/MavlinkLogManager.h index 0ea7c8b..b0c69fb 100644 --- a/src/Vehicle/MavlinkLogManager.h +++ b/src/Vehicle/MavlinkLogManager.h @@ -165,6 +165,8 @@ private slots: void _activeVehicleChanged (Vehicle* vehicle); void _mavlinkLogData (Vehicle* vehicle, uint8_t target_system, uint8_t target_component, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* 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); @@ -173,6 +175,7 @@ private: int _getFirstSelected (); void _insertNewLog (MavlinkLogFiles* newLog); void _deleteLog (MavlinkLogFiles* log); + void _discardLog (); QString _makeFilename (const QString& baseName); private: @@ -191,6 +194,8 @@ private: CurrentRunningLog* _currentSavingFile; uint16_t _sequence; bool _deleteAfterUpload; + int _loggingCmdTryCount; + QTimer _ackTimer; }; #endif diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 6a5044b..f4ac52a 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -562,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); From 8244624177fd010d33e3c6f40b174e318118cf88 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Wed, 26 Oct 2016 17:29:00 -0400 Subject: [PATCH 09/10] Objectify log processor. --- src/Vehicle/MavlinkLogManager.cc | 272 ++++++++++++++++++++++++++++++--------- src/Vehicle/MavlinkLogManager.h | 44 ++++--- src/Vehicle/Vehicle.cc | 6 +- src/Vehicle/Vehicle.h | 2 +- 4 files changed, 239 insertions(+), 85 deletions(-) diff --git a/src/Vehicle/MavlinkLogManager.cc b/src/Vehicle/MavlinkLogManager.cc index 5d6a958..ccb067f 100644 --- a/src/Vehicle/MavlinkLogManager.cc +++ b/src/Vehicle/MavlinkLogManager.cc @@ -106,15 +106,190 @@ MavlinkLogFiles::setUploaded(bool uploaded) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CurrentRunningLog::close() +MavlinkLogProcessor::MavlinkLogProcessor() + : _fd(NULL) + , _written(0) + , _sequence(-1) + , _numDrops(0) + , _gotHeader(false) + , _error(false) + , _record(NULL) { - if(fd) { - fclose(fd); - fd = 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) @@ -125,8 +300,7 @@ MavlinkLogManager::MavlinkLogManager(QGCApplication* app) , _vehicle(NULL) , _logRunning(false) , _loggingDisabled(false) - , _currentSavingFile(NULL) - , _sequence(0) + , _logProcessor(NULL) , _deleteAfterUpload(false) , _loggingCmdTryCount(0) { @@ -382,20 +556,20 @@ MavlinkLogManager::stopLogging() //-- Tell vehicle to stop sending logs _vehicle->stopMavlinkLog(); } - if(_currentSavingFile) { - _currentSavingFile->close(); - if(_currentSavingFile->record) { - _currentSavingFile->record->setWriting(false); + if(_logProcessor) { + _logProcessor->close(); + if(_logProcessor->record()) { + _logProcessor->record()->setWriting(false); if(_enableAutoUpload) { //-- Queue log for auto upload (set selected flag) - _currentSavingFile->record->setSelected(true); + _logProcessor->record()->setSelected(true); if(!uploading()) { uploadLog(); } } } - delete _currentSavingFile; - _currentSavingFile = NULL; + delete _logProcessor; + _logProcessor = NULL; _logRunning = false; if(_vehicle) { //-- Setup a timer to make sure vehicle received the command @@ -619,42 +793,24 @@ MavlinkLogManager::_processCmdAck() //----------------------------------------------------------------------------- void -MavlinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t length, uint8_t first_message, const uint8_t* data, bool /*acked*/) +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(_currentSavingFile && _currentSavingFile->fd) { - if(sequence != _sequence) { - qCWarning(MavlinkLogManagerLog) << "Dropped Mavlink log data"; - if(first_message < 255) { - data += first_message; - length -= first_message; - } else { - return; - } - } - if(fwrite(data, 1, length, _currentSavingFile->fd) != (size_t)length) { - qCCritical(MavlinkLogManagerLog) << "Error writing Mavlink log file:" << _currentSavingFile->fileName; - delete _currentSavingFile; - _currentSavingFile = NULL; + 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 { - length = 0; qCWarning(MavlinkLogManagerLog) << "Mavlink log data received when not expected."; } - //-- Update file size - if(_currentSavingFile) { - if(_currentSavingFile->record) { - quint32 size = _currentSavingFile->record->size() + length; - _currentSavingFile->record->setSize(size); - } - } - _sequence = sequence + 1; } //----------------------------------------------------------------------------- @@ -682,13 +838,13 @@ void MavlinkLogManager::_discardLog() { //-- Delete (empty) log file (and record) - if(_currentSavingFile) { - _currentSavingFile->close(); - if(_currentSavingFile->record) { - _deleteLog(_currentSavingFile->record); + if(_logProcessor) { + _logProcessor->close(); + if(_logProcessor->record()) { + _deleteLog(_logProcessor->record()); } - delete _currentSavingFile; - _currentSavingFile = NULL; + delete _logProcessor; + _logProcessor = NULL; } _logRunning = false; emit logRunningChanged(); @@ -698,30 +854,20 @@ MavlinkLogManager::_discardLog() bool MavlinkLogManager::_createNewLog() { - if(_currentSavingFile) { - delete _currentSavingFile; - _currentSavingFile = NULL; + if(_logProcessor) { + delete _logProcessor; + _logProcessor = NULL; } - _currentSavingFile = new CurrentRunningLog; - _currentSavingFile->fileName.sprintf("%s/%03d-%s%s", - _logPath.toLatin1().data(), - _vehicle->id(), - QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data(), - kUlogExtension); - _currentSavingFile->fd = fopen(_currentSavingFile->fileName.toLatin1().data(), "wb"); - if(_currentSavingFile->fd) { - MavlinkLogFiles* newLog = new MavlinkLogFiles(this, _currentSavingFile->fileName, true); - newLog->setWriting(true); - _insertNewLog(newLog); - _currentSavingFile->record = newLog; + _logProcessor = new MavlinkLogProcessor; + if(_logProcessor->create(this, _logPath, _vehicle->id())) { + _insertNewLog(_logProcessor->record()); emit logFilesChanged(); } else { - qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _currentSavingFile->fileName; - delete _currentSavingFile; - _currentSavingFile = NULL; + qCCritical(MavlinkLogManagerLog) << "Could not create Mavlink log file:" << _logProcessor->fileName(); + delete _logProcessor; + _logProcessor = NULL; } - _sequence = 0; - return _currentSavingFile != NULL; + return _logProcessor != NULL; } //----------------------------------------------------------------------------- diff --git a/src/Vehicle/MavlinkLogManager.h b/src/Vehicle/MavlinkLogManager.h index b0c69fb..b39d252 100644 --- a/src/Vehicle/MavlinkLogManager.h +++ b/src/Vehicle/MavlinkLogManager.h @@ -73,24 +73,31 @@ private: }; //----------------------------------------------------------------------------- -class CurrentRunningLog +class MavlinkLogProcessor { public: - CurrentRunningLog() - : fd(NULL) - , record(NULL) - , written(0) - { - } - ~CurrentRunningLog() - { - close(); - } - void close(); - FILE* fd; - QString fileName; - MavlinkLogFiles* record; - quint32 written; + 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; }; //----------------------------------------------------------------------------- @@ -163,7 +170,7 @@ private slots: 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 length, uint8_t first_message, const uint8_t* data, bool acked); + 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 (); @@ -191,8 +198,7 @@ private: Vehicle* _vehicle; bool _logRunning; bool _loggingDisabled; - CurrentRunningLog* _currentSavingFile; - uint16_t _sequence; + MavlinkLogProcessor* _logProcessor; bool _deleteAfterUpload; int _loggingCmdTryCount; QTimer _ackTimer; diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index f4ac52a..c9ed3a8 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -2015,7 +2015,8 @@ 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.length, log.first_message_offset, log.data, false); + emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence, + log.first_message_offset, QByteArray((const char*)log.data, log.length), false); } //----------------------------------------------------------------------------- @@ -2025,7 +2026,8 @@ 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.length, log.first_message_offset, log.data, true); + emit mavlinkLogData(this, log.target_system, log.target_component, log.sequence, + log.first_message_offset, QByteArray((const char*)log.data, log.length), true); } //----------------------------------------------------------------------------- diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 372a848..0f62435 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -643,7 +643,7 @@ signals: 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 length, uint8_t first_message, const uint8_t* data, bool acked); + 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); From c77d8df1ba52cea8d374f77e8adabc2ff9ec2d7f Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Wed, 26 Oct 2016 17:47:29 -0400 Subject: [PATCH 10/10] Make sure user knows an email is needed for log uploading. --- src/ui/preferences/MavlinkSettings.qml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ui/preferences/MavlinkSettings.qml b/src/ui/preferences/MavlinkSettings.qml index 3beaa60..b98e2b5 100644 --- a/src/ui/preferences/MavlinkSettings.qml +++ b/src/ui/preferences/MavlinkSettings.qml @@ -57,8 +57,8 @@ Rectangle { visible: false icon: StandardIcon.Warning standardButtons: StandardButton.Close - title: qsTr("Uploading Log Files") - text: qsTr("Please enter an email address before uploading log files.") + title: qsTr("MAVLink Logging") + text: qsTr("Please enter an email address before uploading MAVLink log files.") } QGCFlickable { @@ -235,6 +235,11 @@ Rectangle { 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") + } } } } @@ -283,9 +288,14 @@ Rectangle { id: autoUploadCheck text: qsTr("Enable automatic log uploads") checked: QGroundControl.mavlinkLogManager.enableAutoUpload - enabled: emailField.text !== "" && urlField !== "" onClicked: { - QGroundControl.mavlinkLogManager.enableAutoUpload = checked + QGroundControl.mavlinkLogManager.emailAddress = emailField.text + if(checked && QGroundControl.mavlinkLogManager.emailAddress === "") { + checked = false + emptyEmailDialog.open() + } else { + QGroundControl.mavlinkLogManager.enableAutoUpload = checked + } } } //----------------------------------------------------------------- @@ -293,7 +303,7 @@ Rectangle { QGCCheckBox { text: qsTr("Delete log file after uploading") checked: QGroundControl.mavlinkLogManager.deleteAfterUpload - enabled: emailField.text !== "" && urlField !== "" && autoUploadCheck.checked + enabled: autoUploadCheck.checked onClicked: { QGroundControl.mavlinkLogManager.deleteAfterUpload = checked }