diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index 2174a90..f226a05 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -356,6 +356,8 @@ HEADERS += \
src/VehicleSetup/JoystickConfigController.h \
src/ViewWidgets/CustomCommandWidget.h \
src/ViewWidgets/CustomCommandWidgetController.h \
+ src/ViewWidgets/LogDownload.h \
+ src/ViewWidgets/LogDownloadController.h \
src/ViewWidgets/ViewWidgetController.h \
}
@@ -468,6 +470,8 @@ SOURCES += \
src/VehicleSetup/JoystickConfigController.cc \
src/ViewWidgets/CustomCommandWidget.cc \
src/ViewWidgets/CustomCommandWidgetController.cc \
+ src/ViewWidgets/LogDownload.cc \
+ src/ViewWidgets/LogDownloadController.cc \
src/ViewWidgets/ViewWidgetController.cc \
}
diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc
index 8d3a851..37046ce 100644
--- a/qgroundcontrol.qrc
+++ b/qgroundcontrol.qrc
@@ -11,6 +11,7 @@
src/AutoPilotPlugins/APM/APMAirframeComponent.qml
src/AutoPilotPlugins/APM/APMAirframeComponentSummary.qml
src/ViewWidgets/CustomCommandWidget.qml
+ src/ViewWidgets/LogDownload.qml
src/VehicleSetup/FirmwareUpgrade.qml
src/FlightDisplay/FlightDisplayView.qml
src/AutoPilotPlugins/PX4/FlightModesComponent.qml
diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index f7922cb..8a34881 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -95,6 +95,7 @@
#include "FlightDisplayViewController.h"
#include "VideoSurface.h"
#include "VideoReceiver.h"
+#include "LogDownloadController.h"
#ifndef __ios__
#include "SerialLink.h"
@@ -377,7 +378,7 @@ void QGCApplication::_initCommon(void)
qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "JoystickManager", "Reference only");
qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "Joystick", "Reference only");
- qmlRegisterType ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
+ qmlRegisterType ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "APMFlightModesComponentController");
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "FlightModesComponentController");
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "APMAirframeComponentController");
@@ -396,6 +397,7 @@ void QGCApplication::_initCommon(void)
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController");
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController");
qmlRegisterType ("QGroundControl.Controllers", 1, 0, "JoystickConfigController");
+ qmlRegisterType ("QGroundControl.Controllers", 1, 0, "LogDownloadController");
#endif
// Register Qml Singletons
diff --git a/src/QGCDockWidget.cc b/src/QGCDockWidget.cc
index b17cbc9..0adb405 100644
--- a/src/QGCDockWidget.cc
+++ b/src/QGCDockWidget.cc
@@ -36,7 +36,6 @@ QGCDockWidget::QGCDockWidget(const QString& title, QAction* action, QWidget* par
if (action) {
setWindowTitle(title);
setWindowFlags(Qt::Tool);
-
loadSettings();
}
}
@@ -55,11 +54,11 @@ void QGCDockWidget::loadSettings(void)
{
if (_action) {
QSettings settings;
-
settings.beginGroup(_settingsGroup);
if (settings.contains(_title)) {
restoreGeometry(settings.value(_title).toByteArray());
}
+ settings.endGroup();
}
}
@@ -67,8 +66,8 @@ void QGCDockWidget::saveSettings(void)
{
if (_action) {
QSettings settings;
-
settings.beginGroup(_settingsGroup);
settings.setValue(_title, saveGeometry());
+ settings.endGroup();
}
}
diff --git a/src/ViewWidgets/LogDownload.cc b/src/ViewWidgets/LogDownload.cc
new file mode 100644
index 0000000..f30eee3
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.cc
@@ -0,0 +1,33 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2010 QGROUNDCONTROL PROJECT
+
+This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with QGROUNDCONTROL. If not, see .
+
+======================================================================*/
+
+#include "LogDownload.h"
+
+LogDownload::LogDownload(const QString& title, QAction* action, QWidget *parent) :
+ QGCQmlWidgetHolder(title, action, parent)
+{
+ Q_UNUSED(title);
+ Q_UNUSED(action);
+ setSource(QUrl::fromUserInput("qrc:/qml/LogDownload.qml"));
+ loadSettings();
+}
diff --git a/src/ViewWidgets/LogDownload.h b/src/ViewWidgets/LogDownload.h
new file mode 100644
index 0000000..d22a3f2
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.h
@@ -0,0 +1,37 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT
+
+This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with QGROUNDCONTROL. If not, see .
+
+======================================================================*/
+
+#ifndef LogDownload_H
+#define LogDownload_H
+
+#include "QGCQmlWidgetHolder.h"
+
+class LogDownload : public QGCQmlWidgetHolder
+{
+ Q_OBJECT
+
+public:
+ LogDownload(const QString& title, QAction* action, QWidget *parent = 0);
+};
+
+#endif
diff --git a/src/ViewWidgets/LogDownload.qml b/src/ViewWidgets/LogDownload.qml
new file mode 100644
index 0000000..e609235
--- /dev/null
+++ b/src/ViewWidgets/LogDownload.qml
@@ -0,0 +1,210 @@
+/*=====================================================================
+
+QGroundControl Open Source Ground Control Station
+
+(c) 2009, 2015 QGROUNDCONTROL PROJECT
+
+This file is part of the QGROUNDCONTROL project
+
+QGROUNDCONTROL is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+QGROUNDCONTROL is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with QGROUNDCONTROL. If not, see .
+
+======================================================================*/
+
+import QtQuick 2.5
+import QtQuick.Controls 1.2
+import QtQuick.Controls.Styles 1.2
+import QtQuick.Dialogs 1.2
+
+import QGroundControl.Palette 1.0
+import QGroundControl.Controls 1.0
+import QGroundControl.Controllers 1.0
+import QGroundControl.ScreenTools 1.0
+
+QGCView {
+ viewPanel: panel
+
+ property real _margins: ScreenTools.defaultFontPixelHeight
+
+ function numberWithCommas(x) {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",").replace(/,,/g, ",");
+ }
+
+ LogDownloadController {
+ id: controller
+ factPanel: panel
+ onSelectionChanged: {
+ tableView.selection.clear()
+ for(var i = 0; i < controller.model.count; i++) {
+ var o = controller.model.get(i)
+ if (o && o.selected) {
+ tableView.selection.select(i, i)
+ }
+ }
+ }
+ }
+
+ QGCPalette { id: palette; colorGroupEnabled: enabled }
+
+ QGCViewPanel {
+ id: panel
+ anchors.fill: parent
+
+ TableView {
+ id: tableView
+ anchors.margins: _margins
+ anchors.left: parent.left
+ anchors.right: refreshButton.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ model: controller.model
+ selectionMode: SelectionMode.MultiSelection
+
+ TableViewColumn {
+ title: "Id"
+ width: ScreenTools.defaultFontPixelWidth * 4
+ horizontalAlignment: Text.AlignHCenter
+ delegate : Text {
+ horizontalAlignment: Text.AlignHCenter
+ text: {
+ var o = controller.model.get(styleData.row)
+ return o ? o.id : ""
+ }
+ }
+ }
+
+ TableViewColumn {
+ title: "Date"
+ width: ScreenTools.defaultFontPixelWidth * 30
+ horizontalAlignment: Text.AlignHCenter
+ delegate : Text {
+ text: {
+ var o = controller.model.get(styleData.row)
+ if (o) {
+ //-- Have we received this entry already?
+ if(controller.model.get(styleData.row).received) {
+ var d = controller.model.get(styleData.row).time
+ if(d.getUTCFullYear() < 1980)
+ return "Date Unknown"
+ else
+ return d.toLocaleString()
+ }
+ }
+ return ""
+ }
+ }
+ }
+
+ TableViewColumn {
+ title: "Size"
+ width: ScreenTools.defaultFontPixelWidth * 12
+ horizontalAlignment: Text.AlignHCenter
+ delegate : Text {
+ horizontalAlignment: Text.AlignRight
+ text: {
+ var o = controller.model.get(styleData.row)
+ return o ? numberWithCommas(o.size) : ""
+ }
+ }
+ }
+
+ TableViewColumn {
+ title: "Status"
+ width: ScreenTools.defaultFontPixelWidth * 18
+ horizontalAlignment: Text.AlignHCenter
+ delegate : Text {
+ horizontalAlignment: Text.AlignHCenter
+ text: {
+ var o = controller.model.get(styleData.row)
+ return o ? o.status : ""
+ }
+ }
+ }
+
+ }
+
+ QGCButton {
+ id: refreshButton
+ anchors.margins: _margins
+ anchors.top: parent.top
+ anchors.right: parent.right
+ enabled: !controller.requestingList && !controller.downloadingLogs
+ text: "Refresh"
+ onClicked: {
+ controller.refresh()
+ }
+ }
+
+ QGCButton {
+ id: downloadButton
+ anchors.margins: _margins
+ anchors.top: refreshButton.bottom
+ anchors.right: parent.right
+ enabled: !controller.requestingList && !controller.downloadingLogs && tableView.selection.count > 0
+ text: "Download"
+ onClicked: {
+ //-- Clear selection
+ for(var i = 0; i < controller.model.count; i++) {
+ var o = controller.model.get(i)
+ if (o) o.selected = false
+ }
+ //-- Flag selected log files
+ tableView.selection.forEach(function(rowIndex){
+ var o = controller.model.get(rowIndex)
+ if (o) o.selected = true
+ })
+ //-- Download them
+ controller.download()
+ }
+ }
+
+ QGCButton {
+ id: eraseAllButton
+ anchors.margins: _margins
+ anchors.top: downloadButton.bottom
+ anchors.right: parent.right
+ enabled: !controller.requestingList && !controller.downloadingLogs && controller.model.count > 0
+ text: "Erase All"
+ onClicked: {
+ eraseAllDialog.visible = true
+ }
+ MessageDialog {
+ id: eraseAllDialog
+ visible: false
+ icon: StandardIcon.Warning
+ standardButtons: StandardButton.Yes | StandardButton.No
+ title: "Delete All Log Files"
+ text: "All log files will be erased permanently. Is this really what you want?"
+ onYes: {
+ controller.eraseAll()
+ eraseAllDialog.visible = false
+ }
+ onNo: {
+ eraseAllDialog.visible = false
+ }
+ }
+ }
+
+ QGCButton {
+ id: cancelButton
+ anchors.margins: _margins
+ anchors.top: eraseAllButton.bottom
+ anchors.right: parent.right
+ text: "Cancel"
+ enabled: controller.requestingList || controller.downloadingLogs
+ onClicked: {
+ controller.cancel()
+ }
+ }
+ }
+}
diff --git a/src/ViewWidgets/LogDownloadController.cc b/src/ViewWidgets/LogDownloadController.cc
new file mode 100644
index 0000000..445d946
--- /dev/null
+++ b/src/ViewWidgets/LogDownloadController.cc
@@ -0,0 +1,627 @@
+/*=====================================================================
+
+ QGroundControl Open Source Ground Control Station
+
+ (c) 2009 - 2014 QGROUNDCONTROL PROJECT
+
+ This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with QGROUNDCONTROL. If not, see .
+
+ ======================================================================*/
+
+#include "LogDownloadController.h"
+#include "MultiVehicleManager.h"
+#include "QGCMAVLink.h"
+#include "QGCFileDialog.h"
+#include "UAS.h"
+#include "QGCApplication.h"
+#include "QGCToolbox.h"
+#include "Vehicle.h"
+#include "MainWindow.h"
+
+#include
+#include
+#include
+
+#define kTimeOutMilliseconds 500
+
+QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog")
+
+//----------------------------------------------------------------------------------------
+LogDownloadData::LogDownloadData(QGCLogEntry* entry_)
+ : ID(entry_->id())
+ , entry(entry_)
+ , written(0)
+{
+
+}
+
+//----------------------------------------------------------------------------------------
+QGCLogEntry:: QGCLogEntry(uint logId, const QDateTime& dateTime, uint logSize, bool received)
+ : _logID(logId)
+ , _logSize(logSize)
+ , _logTimeUTC(dateTime)
+ , _received(received)
+ , _selected(false)
+{
+ _status = "Pending";
+}
+
+//----------------------------------------------------------------------------------------
+LogDownloadController::LogDownloadController(void)
+ : _uas(NULL)
+ , _downloadData(NULL)
+ , _vehicle(NULL)
+ , _requestingLogEntries(false)
+ , _downloadingLogs(false)
+ , _retries(0)
+{
+ MultiVehicleManager *manager = qgcApp()->toolbox()->multiVehicleManager();
+ connect(manager, &MultiVehicleManager::activeVehicleChanged, this, &LogDownloadController::_setActiveVehicle);
+ connect(&_timer, &QTimer::timeout, this, &LogDownloadController::_processDownload);
+ _setActiveVehicle(manager->activeVehicle());
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_processDownload()
+{
+ if(_requestingLogEntries) {
+ _findMissingEntries();
+ } else if(_downloadingLogs) {
+ _findMissingData();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_setActiveVehicle(Vehicle* vehicle)
+{
+ if((_uas && vehicle && _uas == vehicle->uas()) || !vehicle ) {
+ return;
+ }
+ _vehicle = vehicle;
+ if (_uas) {
+ _logEntriesModel.clear();
+ disconnect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
+ disconnect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
+ _uas = NULL;
+ }
+ _uas = vehicle->uas();
+ connect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
+ connect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_logEntry(UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t /*last_log_num*/)
+{
+ //-- Do we care?
+ if(!_uas || uas != _uas || !_requestingLogEntries) {
+ return;
+ }
+ //-- If this is the first, pre-fill it
+ if(!_logEntriesModel.count() && num_logs > 0) {
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry *entry = new QGCLogEntry(i);
+ _logEntriesModel.append(entry);
+ }
+ }
+ //-- Update this log record
+ if(num_logs > 0) {
+ if(id < _logEntriesModel.count()) {
+ QGCLogEntry* entry = _logEntriesModel[id];
+ entry->setSize(size);
+ entry->setTime(QDateTime::fromTime_t(time_utc));
+ entry->setReceived(true);
+ entry->setStatus(QString("Available"));
+ } else {
+ qWarning() << "Received log entry for out-of-bound index:" << id;
+ }
+ } else {
+ //-- No logs to list
+ _receivedAllEntries();
+ }
+ //-- Reset retry count
+ _retries = 0;
+ //-- Do we have it all?
+ if(_entriesComplete()) {
+ _receivedAllEntries();
+ } else {
+ //-- Reset timer
+ _timer.start(kTimeOutMilliseconds);
+ }
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_entriesComplete()
+{
+ //-- Iterate entries and look for a gap
+ int num_logs = _logEntriesModel.count();
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry* entry = _logEntriesModel[i];
+ if(entry) {
+ if(!entry->received()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_resetSelection()
+{
+ int num_logs = _logEntriesModel.count();
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry* entry = _logEntriesModel[i];
+ if(entry) {
+ entry->setSelected(false);
+ }
+ }
+ emit selectionChanged();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_receivedAllEntries()
+{
+ _timer.stop();
+ _requestingLogEntries = false;
+ emit requestingListChanged();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_findMissingEntries()
+{
+ int start = -1;
+ int end = -1;
+ int num_logs = _logEntriesModel.count();
+ //-- Iterate entries and look for a gap
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry* entry = _logEntriesModel[i];
+ if(entry) {
+ if(!entry->received()) {
+ if(start < 0)
+ start = i;
+ else
+ end = i;
+ } else {
+ if(start >= 0) {
+ break;
+ }
+ }
+ }
+ }
+ //-- Is there something missing?
+ if(start >= 0) {
+ //-- Have we tried too many times?
+ if(_retries++ > 2) {
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry* entry = _logEntriesModel[i];
+ if(entry && !entry->received()) {
+ entry->setStatus(QString("Error"));
+ }
+ }
+ //-- Give up
+ _receivedAllEntries();
+ qWarning() << "Too many errors retreiving log list. Giving up.";
+ return;
+ }
+ //-- Is it a sequence or just one entry?
+ if(end < 0) {
+ end = start;
+ }
+ //-- Request these entries again
+ _requestLogList((uint32_t)start, (uint32_t) end);
+ } else {
+ _receivedAllEntries();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data)
+{
+ if(!_uas || uas != _uas || !_downloadData) {
+ return;
+ }
+ if(_downloadData->ID != id) {
+ qWarning() << "Received log data for wrong log";
+ return;
+ }
+ bool result = false;
+ //-- Find offset table entry
+ uint o_index = ofs / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
+ if(o_index <= (uint)_downloadData->offsets.count()) {
+ _downloadData->offsets[o_index] = count;
+ //-- Write chunk to file
+ if(_downloadData->file.seek(ofs)) {
+ if(_downloadData->file.write((const char*)data, count)) {
+ _downloadData->written += count;
+ //-- Update status
+ _downloadData->entry->setStatus(QString::number(_downloadData->written));
+ result = true;
+ //-- reset retries
+ _retries = 0;
+ //-- Reset timer
+ _timer.start(kTimeOutMilliseconds);
+ //-- Do we have it all?
+ if(_logComplete()) {
+ _downloadData->entry->setStatus(QString("Downloaded"));
+ //-- Check for more
+ _receivedAllData();
+ }
+ } else {
+ qWarning() << "Error while writing log file chunk";
+ }
+ } else {
+ qWarning() << "Error while seeking log file offset";
+ }
+ } else {
+ qWarning() << "Received log offset greater than expected";
+ }
+ if(!result) {
+ _downloadData->entry->setStatus(QString("Error"));
+ }
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_logComplete()
+{
+ //-- Iterate entries and look for a gap
+ int num_ofs = _downloadData->offsets.count();
+ for(int i = 0; i < num_ofs; i++) {
+ if(_downloadData->offsets[i] == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_receivedAllData()
+{
+ _timer.stop();
+ //-- Anything queued up for download?
+ if(_prepareLogDownload()) {
+ //-- Request Log
+ _requestLogData(_downloadData->ID, 0, _downloadData->entry->size());
+ } else {
+ _resetSelection();
+ _downloadingLogs = false;
+ emit downloadingLogsChanged();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_findMissingData()
+{
+ int start = -1;
+ int end = -1;
+ int num_ofs = _downloadData->offsets.count();
+ //-- Iterate offsets and look for a gap
+ for(int i = 0; i < num_ofs; i++) {
+ if(_downloadData->offsets[i] == 0) {
+ if(start < 0)
+ start = i;
+ else
+ end = i;
+ } else {
+ if(start >= 0) {
+ break;
+ }
+ }
+ }
+ //-- Is there something missing?
+ if(start >= 0) {
+ //-- Have we tried too many times?
+ if(_retries++ > 2) {
+ _downloadData->entry->setStatus(QString("Timed Out"));
+ //-- Give up
+ qWarning() << "Too many errors retreiving log data. Giving up.";
+ _receivedAllData();
+ return;
+ }
+ //-- Is it a sequence or just one entry?
+ if(end < 0) {
+ end = start;
+ }
+ //-- Request these log chunks again
+ _requestLogData(
+ _downloadData->ID,
+ (uint32_t)(start * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN),
+ (uint32_t)((end - start + 1) * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN));
+ } else {
+ _receivedAllData();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_requestLogData(uint8_t id, uint32_t offset, uint32_t count)
+{
+ if(_vehicle) {
+ qCDebug(LogDownloadLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << ")";
+ mavlink_message_t msg;
+ mavlink_msg_log_request_data_pack(
+ qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+ qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+ &msg,
+ qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), MAV_COMP_ID_ALL,
+ id, offset, count);
+ _vehicle->sendMessage(msg);
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::refresh(void)
+{
+ _logEntriesModel.clear();
+ _requestLogList();
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::_requestLogList(uint32_t start, uint32_t end)
+{
+ if(_vehicle && _uas) {
+ qCDebug(LogDownloadLog) << "Request log entry list (" << start << "through" << end << ")";
+ _requestingLogEntries = true;
+ emit requestingListChanged();
+ mavlink_message_t msg;
+ mavlink_msg_log_request_list_pack(
+ qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+ qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+ &msg,
+ _vehicle->id(),
+ MAV_COMP_ID_ALL,
+ start,
+ end);
+ _vehicle->sendMessage(msg);
+ //-- Wait 2 seconds before bitching about not getting anything
+ _timer.start(2000);
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::download(void)
+{
+ //-- Stop listing just in case
+ _receivedAllEntries();
+ //-- Reset downloads, again just in case
+ if(_downloadData) {
+ delete _downloadData;
+ _downloadData = 0;
+ }
+ _downloadPath.clear();
+ _downloadPath = QGCFileDialog::getExistingDirectory(
+ MainWindow::instance(),
+ "Log Download Directory",
+ QDir::homePath(),
+ QGCFileDialog::ShowDirsOnly | QGCFileDialog::DontResolveSymlinks);
+ if(!_downloadPath.isEmpty()) {
+ if(!_downloadPath.endsWith(QDir::separator()))
+ _downloadPath += QDir::separator();
+ //-- Start download process
+ _downloadingLogs = true;
+ emit downloadingLogsChanged();
+ _receivedAllData();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+QGCLogEntry*
+LogDownloadController::_getNextSelected()
+{
+ //-- Iterate entries and look for a selected file
+ int num_logs = _logEntriesModel.count();
+ for(int i = 0; i < num_logs; i++) {
+ QGCLogEntry* entry = _logEntriesModel[i];
+ if(entry) {
+ if(entry->selected()) {
+ return entry;
+ }
+ }
+ }
+ return NULL;
+}
+
+//----------------------------------------------------------------------------------------
+bool
+LogDownloadController::_prepareLogDownload()
+{
+ if(_downloadData) {
+ delete _downloadData;
+ _downloadData = NULL;
+ }
+ QGCLogEntry* entry = _getNextSelected();
+ if(!entry) {
+ return false;
+ }
+ //-- Deselect file
+ entry->setSelected(false);
+ emit selectionChanged();
+ bool result = false;
+ QString ftime;
+ if(entry->time().date().year() < 1980) {
+ ftime = "UnknownDate";
+ } else {
+ ftime = entry->time().toString("yyyy-M-d-hh-mm-ss");
+ }
+ _downloadData = new LogDownloadData(entry);
+ _downloadData->filename = QString("log_") + QString::number(entry->id()) + "_" + ftime + ".txt";
+ _downloadData->file.setFileName(_downloadPath + _downloadData->filename);
+ //-- Append a number to the end if the filename already exists
+ if (_downloadData->file.exists()){
+ uint num_dups = 0;
+ QStringList filename_spl = _downloadData->filename.split('.');
+ do {
+ num_dups +=1;
+ _downloadData->file.setFileName(filename_spl[0] + '_' + QString::number(num_dups) + '.' + filename_spl[1]);
+ } while( _downloadData->file.exists());
+ }
+ //-- Create file
+ if (!_downloadData->file.open(QIODevice::WriteOnly)) {
+ qWarning() << "Failed to create log file:" << _downloadData->filename;
+ } else {
+ //-- Preallocate file
+ if(!_downloadData->file.resize(entry->size())) {
+ qWarning() << "Failed to allocate space for log file:" << _downloadData->filename;
+ } else {
+ //-- Prepare Offset Table
+ uint o_count = (uint)ceil(entry->size() / (double)MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
+ for(uint i = 0; i < o_count; i++) {
+ _downloadData->offsets.append(0);
+ }
+ result = true;
+ }
+ }
+ if(!result) {
+ if (_downloadData->file.exists()) {
+ _downloadData->file.remove();
+ }
+ _downloadData->entry->setStatus(QString("Error"));
+ delete _downloadData;
+ _downloadData = NULL;
+ }
+ return result;
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::eraseAll(void)
+{
+ if(_vehicle && _uas) {
+ mavlink_message_t msg;
+ mavlink_msg_log_erase_pack(
+ qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
+ qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
+ &msg,
+ qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), MAV_COMP_ID_ALL);
+ _vehicle->sendMessage(msg);
+ refresh();
+ }
+}
+
+//----------------------------------------------------------------------------------------
+void
+LogDownloadController::cancel(void)
+{
+ if(_uas){
+ _receivedAllEntries();
+ }
+ if(_downloadData) {
+ _downloadData->entry->setStatus(QString("Canceled"));
+ if (_downloadData->file.exists()) {
+ _downloadData->file.remove();
+ }
+ delete _downloadData;
+ _downloadData = 0;
+ }
+ _resetSelection();
+ _downloadingLogs = false;
+ emit downloadingLogsChanged();
+}
+
+//-----------------------------------------------------------------------------
+QGCLogModel::QGCLogModel(QObject* parent)
+ : QAbstractListModel(parent)
+{
+
+}
+
+//-----------------------------------------------------------------------------
+QGCLogEntry*
+QGCLogModel::get(int index)
+{
+ if (index < 0 || index >= _logEntries.count()) {
+ return NULL;
+ }
+ return _logEntries[index];
+}
+
+//-----------------------------------------------------------------------------
+int
+QGCLogModel::count() const
+{
+ return _logEntries.count();
+}
+
+//-----------------------------------------------------------------------------
+void
+QGCLogModel::append(QGCLogEntry* object)
+{
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ _logEntries.append(object);
+ endInsertRows();
+ emit countChanged();
+}
+
+//-----------------------------------------------------------------------------
+void
+QGCLogModel::clear(void)
+{
+ if(!_logEntries.isEmpty()) {
+ beginRemoveRows(QModelIndex(), 0, _logEntries.count());
+ while (_logEntries.count()) {
+ QGCLogEntry* entry = _logEntries.last();
+ if(entry) delete entry;
+ _logEntries.removeLast();
+ }
+ endRemoveRows();
+ emit countChanged();
+ }
+}
+
+//-----------------------------------------------------------------------------
+QGCLogEntry*
+QGCLogModel::operator[](int index)
+{
+ return get(index);
+}
+
+//-----------------------------------------------------------------------------
+int
+QGCLogModel::rowCount(const QModelIndex& /*parent*/) const
+{
+ return _logEntries.count();
+}
+
+//-----------------------------------------------------------------------------
+QVariant
+QGCLogModel::data(const QModelIndex & index, int role) const {
+ if (index.row() < 0 || index.row() >= _logEntries.count())
+ return QVariant();
+ if (role == ObjectRole)
+ return QVariant::fromValue(_logEntries[index.row()]);
+ return QVariant();
+}
+
+//-----------------------------------------------------------------------------
+QHash
+QGCLogModel::roleNames() const {
+ QHash roles;
+ roles[ObjectRole] = "logEntry";
+ return roles;
+}
diff --git a/src/ViewWidgets/LogDownloadController.h b/src/ViewWidgets/LogDownloadController.h
new file mode 100644
index 0000000..3c29836
--- /dev/null
+++ b/src/ViewWidgets/LogDownloadController.h
@@ -0,0 +1,193 @@
+/*=====================================================================
+
+ QGroundControl Open Source Ground Control Station
+
+ (c) 2009 - 2014 QGROUNDCONTROL PROJECT
+
+ This file is part of the QGROUNDCONTROL project
+
+ QGROUNDCONTROL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QGROUNDCONTROL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with QGROUNDCONTROL. If not, see .
+
+ ======================================================================*/
+
+#ifndef LogDownloadController_H
+#define LogDownloadController_H
+
+#include
+#include
+#include
+
+#include
+
+#include "UASInterface.h"
+#include "AutoPilotPlugin.h"
+#include "FactPanelController.h"
+
+class MultiVehicleManager;
+class UASInterface;
+class Vehicle;
+class QGCLogEntry;
+class LogDownloadData;
+
+Q_DECLARE_LOGGING_CATEGORY(LogDownloadLog)
+
+//-----------------------------------------------------------------------------
+class QGCLogModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+
+ enum QGCLogModelRoles {
+ ObjectRole = Qt::UserRole + 1
+ };
+
+ QGCLogModel(QObject *parent = 0);
+
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_INVOKABLE QGCLogEntry* get(int index);
+
+ int count (void) const;
+ void append (QGCLogEntry* entry);
+ void clear (void);
+ QGCLogEntry*operator[] (int i);
+
+ int rowCount (const QModelIndex & parent = QModelIndex()) const;
+ QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+signals:
+ void countChanged ();
+
+protected:
+ QHash roleNames() const;
+private:
+ QList _logEntries;
+};
+
+//-----------------------------------------------------------------------------
+class QGCLogEntry : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(uint id READ id CONSTANT)
+ Q_PROPERTY(QDateTime time READ time NOTIFY timeChanged)
+ Q_PROPERTY(uint size READ size NOTIFY sizeChanged)
+ Q_PROPERTY(bool received READ received NOTIFY receivedChanged)
+ Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
+ Q_PROPERTY(QString status READ status NOTIFY statusChanged)
+
+public:
+ QGCLogEntry(uint logId, const QDateTime& dateTime = QDateTime(), uint logSize = 0, bool received = false);
+
+ uint id () const { return _logID; }
+ uint size () const { return _logSize; }
+ QDateTime time () const { return _logTimeUTC; }
+ bool received () const { return _received; }
+ bool selected () const { return _selected; }
+ QString status () const { return _status; }
+
+ void setId (uint id_) { _logID = id_; }
+ void setSize (uint size_) { _logSize = size_; emit sizeChanged(); }
+ void setTime (QDateTime date_) { _logTimeUTC = date_; emit timeChanged(); }
+ void setReceived (bool rec_) { _received = rec_; emit receivedChanged(); }
+ void setSelected (bool sel_) { _selected = sel_; emit selectedChanged(); }
+ void setStatus (QString stat_) { _status = stat_; emit statusChanged(); }
+
+signals:
+ void idChanged ();
+ void timeChanged ();
+ void sizeChanged ();
+ void receivedChanged ();
+ void selectedChanged ();
+ void statusChanged ();
+
+private:
+ uint _logID;
+ uint _logSize;
+ QDateTime _logTimeUTC;
+ bool _received;
+ bool _selected;
+ QString _status;
+};
+
+//-----------------------------------------------------------------------------
+class LogDownloadData {
+public:
+ LogDownloadData(QGCLogEntry* entry);
+ QList offsets;
+ QFile file;
+ QString filename;
+ uint ID;
+ QTimer processDataTimer;
+ QGCLogEntry* entry;
+ uint written;
+};
+
+//-----------------------------------------------------------------------------
+class LogDownloadController : public FactPanelController
+{
+ Q_OBJECT
+public:
+
+ LogDownloadController();
+
+ Q_PROPERTY(QGCLogModel* model READ model NOTIFY modelChanged)
+ Q_PROPERTY(bool requestingList READ requestingList NOTIFY requestingListChanged)
+ Q_PROPERTY(bool downloadingLogs READ downloadingLogs NOTIFY downloadingLogsChanged)
+
+ QGCLogModel* model () { return &_logEntriesModel; }
+ bool requestingList () { return _requestingLogEntries; }
+ bool downloadingLogs () { return _downloadingLogs; }
+
+ Q_INVOKABLE void refresh ();
+ Q_INVOKABLE void download ();
+ Q_INVOKABLE void eraseAll ();
+ Q_INVOKABLE void cancel ();
+
+signals:
+ void requestingListChanged ();
+ void downloadingLogsChanged ();
+ void modelChanged ();
+ void selectionChanged ();
+
+private slots:
+ void _setActiveVehicle (Vehicle* vehicle);
+ 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);
+ void _processDownload ();
+
+private:
+
+ bool _entriesComplete ();
+ bool _logComplete ();
+ void _findMissingEntries();
+ void _receivedAllEntries();
+ void _receivedAllData ();
+ void _resetSelection ();
+ void _findMissingData ();
+ void _requestLogList (uint32_t start = 0, uint32_t end = 0xFFFF);
+ void _requestLogData (uint8_t id, uint32_t offset = 0, uint32_t count = 0xFFFFFFFF);
+ bool _prepareLogDownload();
+
+ QGCLogEntry* _getNextSelected();
+
+ UASInterface* _uas;
+ LogDownloadData* _downloadData;
+ QTimer _timer;
+ QGCLogModel _logEntriesModel;
+ Vehicle* _vehicle;
+ bool _requestingLogEntries;
+ bool _downloadingLogs;
+ int _retries;
+ QString _downloadPath;
+};
+
+#endif
diff --git a/src/uas/UAS.cc b/src/uas/UAS.cc
index 7e8be32..0dbfbb1 100644
--- a/src/uas/UAS.cc
+++ b/src/uas/UAS.cc
@@ -961,6 +961,23 @@ void UAS::receiveMessage(mavlink_message_t message)
emit NavigationControllerDataChanged(this, p.nav_roll, p.nav_pitch, p.nav_bearing, p.target_bearing, p.wp_dist);
}
break;
+
+ case MAVLINK_MSG_ID_LOG_ENTRY:
+ {
+ mavlink_log_entry_t log;
+ mavlink_msg_log_entry_decode(&message, &log);
+ emit logEntry(this, log.time_utc, log.size, log.id, log.num_logs, log.last_log_num);
+ }
+ break;
+
+ case MAVLINK_MSG_ID_LOG_DATA:
+ {
+ mavlink_log_data_t log;
+ mavlink_msg_log_data_decode(&message, &log);
+ emit logData(this, log.ofs, log.id, log.count, log.data);
+ }
+ break;
+
default:
break;
}
diff --git a/src/uas/UASInterface.h b/src/uas/UASInterface.h
index 31cb095..d351fd7 100644
--- a/src/uas/UASInterface.h
+++ b/src/uas/UASInterface.h
@@ -88,25 +88,25 @@ public:
static QColor getNextColor() {
/* Create color map */
static QList colors = QList()
- << QColor(231,72,28)
- << QColor(104,64,240)
- << QColor(203,254,121)
- << QColor(161,252,116)
- << QColor(232,33,47)
- << QColor(116,251,110)
- << QColor(234,38,107)
- << QColor(104,250,138)
+ << QColor(231,72,28)
+ << QColor(104,64,240)
+ << QColor(203,254,121)
+ << QColor(161,252,116)
+ << QColor(232,33,47)
+ << QColor(116,251,110)
+ << QColor(234,38,107)
+ << QColor(104,250,138)
<< QColor(235,43,165)
- << QColor(98,248,176)
- << QColor(236,48,221)
- << QColor(92,247,217)
+ << QColor(98,248,176)
+ << QColor(236,48,221)
+ << QColor(92,247,217)
<< QColor(200,54,238)
- << QColor(87,231,246)
- << QColor(151,59,239)
- << QColor(81,183,244)
+ << QColor(87,231,246)
+ << QColor(151,59,239)
+ << QColor(81,183,244)
<< QColor(75,133,243)
- << QColor(242,255,128)
- << QColor(230,126,23);
+ << QColor(242,255,128)
+ << QColor(230,126,23);
static int nextColor = -1;
if(nextColor == 18){//if at the end of the list
@@ -139,10 +139,10 @@ public:
StartBusConfigActuators,
EndBusConfigActuators,
};
-
+
/// Starts the specified calibration
virtual void startCalibration(StartCalibrationType calType) = 0;
-
+
/// Ends any current calibration
virtual void stopCalibration(void) = 0;
@@ -232,11 +232,11 @@ signals:
*
* Typically this is used to send lowlevel information like the battery voltage to the plotting facilities of
* the groundstation. The data here should be converted to human-readable values before being passed, so ideally
- * SI units.
+ * SI units.
*
* @param uasId ID of this system
* @param name name of the value, e.g. "battery voltage"
- * @param unit The units this variable is in as an abbreviation. For system-dependent (such as raw ADC values) use "raw", for bitfields use "bits", for true/false or on/off use "bool", for unitless values use "-".
+ * @param unit The units this variable is in as an abbreviation. For system-dependent (such as raw ADC values) use "raw", for bitfields use "bits", for true/false or on/off use "bool", for unitless values use "-".
* @param value the value that changed
* @param msec the timestamp of the message, in milliseconds
*/
@@ -323,6 +323,10 @@ signals:
// HOME POSITION / ORIGIN CHANGES
void homePositionChanged(int uas, double lat, double lon, double alt);
+ // 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);
+
protected:
// TIMEOUT CONSTANTS
diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc
index 9617758..df8b7bc 100644
--- a/src/ui/MainWindow.cc
+++ b/src/ui/MainWindow.cc
@@ -65,6 +65,7 @@ This file is part of the QGROUNDCONTROL project
#include "QGCDockWidget.h"
#include "UASInfoWidget.h"
#include "HILDockWidget.h"
+#include "LogDownload.h"
#endif
#ifndef __ios__
@@ -86,7 +87,8 @@ enum DockWidgetTypes {
STATUS_DETAILS,
INFO_VIEW,
HIL_CONFIG,
- ANALYZE
+ ANALYZE,
+ LOG_DOWNLOAD
};
static const char *rgDockWidgetNames[] = {
@@ -96,7 +98,8 @@ static const char *rgDockWidgetNames[] = {
"Status Details",
"Info View",
"HIL Config",
- "Analyze"
+ "Analyze",
+ "Log Download"
};
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
@@ -358,6 +361,9 @@ bool MainWindow::_createInnerDockWidget(const QString& widgetName)
case ONBOARD_FILES:
widget = new QGCUASFileViewMulti(widgetName, action, this);
break;
+ case LOG_DOWNLOAD:
+ widget = new LogDownload(widgetName, action, this);
+ break;
case STATUS_DETAILS:
widget = new UASInfoWidget(widgetName, action, this);
break;