Commit 5a2251ba authored by Gus Grubba's avatar Gus Grubba

Merge pull request #2533 from dogmaphobic/logHandling

Log handling
parents d8c45c77 776ee940
...@@ -356,6 +356,8 @@ HEADERS += \ ...@@ -356,6 +356,8 @@ HEADERS += \
src/VehicleSetup/JoystickConfigController.h \ src/VehicleSetup/JoystickConfigController.h \
src/ViewWidgets/CustomCommandWidget.h \ src/ViewWidgets/CustomCommandWidget.h \
src/ViewWidgets/CustomCommandWidgetController.h \ src/ViewWidgets/CustomCommandWidgetController.h \
src/ViewWidgets/LogDownload.h \
src/ViewWidgets/LogDownloadController.h \
src/ViewWidgets/ViewWidgetController.h \ src/ViewWidgets/ViewWidgetController.h \
} }
...@@ -468,6 +470,8 @@ SOURCES += \ ...@@ -468,6 +470,8 @@ SOURCES += \
src/VehicleSetup/JoystickConfigController.cc \ src/VehicleSetup/JoystickConfigController.cc \
src/ViewWidgets/CustomCommandWidget.cc \ src/ViewWidgets/CustomCommandWidget.cc \
src/ViewWidgets/CustomCommandWidgetController.cc \ src/ViewWidgets/CustomCommandWidgetController.cc \
src/ViewWidgets/LogDownload.cc \
src/ViewWidgets/LogDownloadController.cc \
src/ViewWidgets/ViewWidgetController.cc \ src/ViewWidgets/ViewWidgetController.cc \
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<file alias="APMAirframeComponent.qml">src/AutoPilotPlugins/APM/APMAirframeComponent.qml</file> <file alias="APMAirframeComponent.qml">src/AutoPilotPlugins/APM/APMAirframeComponent.qml</file>
<file alias="APMAirframeComponentSummary.qml">src/AutoPilotPlugins/APM/APMAirframeComponentSummary.qml</file> <file alias="APMAirframeComponentSummary.qml">src/AutoPilotPlugins/APM/APMAirframeComponentSummary.qml</file>
<file alias="CustomCommandWidget.qml">src/ViewWidgets/CustomCommandWidget.qml</file> <file alias="CustomCommandWidget.qml">src/ViewWidgets/CustomCommandWidget.qml</file>
<file alias="LogDownload.qml">src/ViewWidgets/LogDownload.qml</file>
<file alias="FirmwareUpgrade.qml">src/VehicleSetup/FirmwareUpgrade.qml</file> <file alias="FirmwareUpgrade.qml">src/VehicleSetup/FirmwareUpgrade.qml</file>
<file alias="FlightDisplayView.qml">src/FlightDisplay/FlightDisplayView.qml</file> <file alias="FlightDisplayView.qml">src/FlightDisplay/FlightDisplayView.qml</file>
<file alias="FlightModesComponent.qml">src/AutoPilotPlugins/PX4/FlightModesComponent.qml</file> <file alias="FlightModesComponent.qml">src/AutoPilotPlugins/PX4/FlightModesComponent.qml</file>
......
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
#include "FlightDisplayViewController.h" #include "FlightDisplayViewController.h"
#include "VideoSurface.h" #include "VideoSurface.h"
#include "VideoReceiver.h" #include "VideoReceiver.h"
#include "LogDownloadController.h"
#ifndef __ios__ #ifndef __ios__
#include "SerialLink.h" #include "SerialLink.h"
...@@ -396,6 +397,7 @@ void QGCApplication::_initCommon(void) ...@@ -396,6 +397,7 @@ void QGCApplication::_initCommon(void)
qmlRegisterType<CustomCommandWidgetController> ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController"); qmlRegisterType<CustomCommandWidgetController> ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController");
qmlRegisterType<FirmwareUpgradeController> ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController"); qmlRegisterType<FirmwareUpgradeController> ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController");
qmlRegisterType<JoystickConfigController> ("QGroundControl.Controllers", 1, 0, "JoystickConfigController"); qmlRegisterType<JoystickConfigController> ("QGroundControl.Controllers", 1, 0, "JoystickConfigController");
qmlRegisterType<LogDownloadController> ("QGroundControl.Controllers", 1, 0, "LogDownloadController");
#endif #endif
// Register Qml Singletons // Register Qml Singletons
......
...@@ -36,7 +36,6 @@ QGCDockWidget::QGCDockWidget(const QString& title, QAction* action, QWidget* par ...@@ -36,7 +36,6 @@ QGCDockWidget::QGCDockWidget(const QString& title, QAction* action, QWidget* par
if (action) { if (action) {
setWindowTitle(title); setWindowTitle(title);
setWindowFlags(Qt::Tool); setWindowFlags(Qt::Tool);
loadSettings(); loadSettings();
} }
} }
...@@ -55,11 +54,11 @@ void QGCDockWidget::loadSettings(void) ...@@ -55,11 +54,11 @@ void QGCDockWidget::loadSettings(void)
{ {
if (_action) { if (_action) {
QSettings settings; QSettings settings;
settings.beginGroup(_settingsGroup); settings.beginGroup(_settingsGroup);
if (settings.contains(_title)) { if (settings.contains(_title)) {
restoreGeometry(settings.value(_title).toByteArray()); restoreGeometry(settings.value(_title).toByteArray());
} }
settings.endGroup();
} }
} }
...@@ -67,8 +66,8 @@ void QGCDockWidget::saveSettings(void) ...@@ -67,8 +66,8 @@ void QGCDockWidget::saveSettings(void)
{ {
if (_action) { if (_action) {
QSettings settings; QSettings settings;
settings.beginGroup(_settingsGroup); settings.beginGroup(_settingsGroup);
settings.setValue(_title, saveGeometry()); settings.setValue(_title, saveGeometry());
settings.endGroup();
} }
} }
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#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();
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#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
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
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()
}
}
}
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#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 <QDebug>
#include <QSettings>
#include <QUrl>
#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<int, QByteArray>
QGCLogModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[ObjectRole] = "logEntry";
return roles;
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#ifndef LogDownloadController_H
#define LogDownloadController_H
#include <QObject>
#include <QTimer>
#include <QAbstractListModel>
#include <memory>
#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<int, QByteArray> roleNames() const;
private:
QList<QGCLogEntry*> _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<uint> 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
...@@ -961,6 +961,23 @@ void UAS::receiveMessage(mavlink_message_t message) ...@@ -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); emit NavigationControllerDataChanged(this, p.nav_roll, p.nav_pitch, p.nav_bearing, p.target_bearing, p.wp_dist);
} }
break; 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: default:
break; break;
} }
......
...@@ -323,6 +323,10 @@ signals: ...@@ -323,6 +323,10 @@ signals:
// HOME POSITION / ORIGIN CHANGES // HOME POSITION / ORIGIN CHANGES
void homePositionChanged(int uas, double lat, double lon, double alt); 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: protected:
// TIMEOUT CONSTANTS // TIMEOUT CONSTANTS
......
...@@ -65,6 +65,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -65,6 +65,7 @@ This file is part of the QGROUNDCONTROL project
#include "QGCDockWidget.h" #include "QGCDockWidget.h"
#include "UASInfoWidget.h" #include "UASInfoWidget.h"
#include "HILDockWidget.h" #include "HILDockWidget.h"
#include "LogDownload.h"
#endif #endif
#ifndef __ios__ #ifndef __ios__
...@@ -86,7 +87,8 @@ enum DockWidgetTypes { ...@@ -86,7 +87,8 @@ enum DockWidgetTypes {
STATUS_DETAILS, STATUS_DETAILS,
INFO_VIEW, INFO_VIEW,
HIL_CONFIG, HIL_CONFIG,
ANALYZE ANALYZE,
LOG_DOWNLOAD
}; };
static const char *rgDockWidgetNames[] = { static const char *rgDockWidgetNames[] = {
...@@ -96,7 +98,8 @@ static const char *rgDockWidgetNames[] = { ...@@ -96,7 +98,8 @@ static const char *rgDockWidgetNames[] = {
"Status Details", "Status Details",
"Info View", "Info View",
"HIL Config", "HIL Config",
"Analyze" "Analyze",
"Log Download"
}; };
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0])) #define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
...@@ -358,6 +361,9 @@ bool MainWindow::_createInnerDockWidget(const QString& widgetName) ...@@ -358,6 +361,9 @@ bool MainWindow::_createInnerDockWidget(const QString& widgetName)
case ONBOARD_FILES: case ONBOARD_FILES:
widget = new QGCUASFileViewMulti(widgetName, action, this); widget = new QGCUASFileViewMulti(widgetName, action, this);
break; break;
case LOG_DOWNLOAD:
widget = new LogDownload(widgetName, action, this);
break;
case STATUS_DETAILS: case STATUS_DETAILS:
widget = new UASInfoWidget(widgetName, action, this); widget = new UASInfoWidget(widgetName, action, this);
break; break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment