11 changed files with 1152 additions and 23 deletions
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/*=====================================================================
|
||||
|
||||
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(); |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 |
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
/*===================================================================== |
||||
|
||||
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() |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,621 @@
@@ -0,0 +1,621 @@
|
||||
/*=====================================================================
|
||||
|
||||
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()) { |
||||
for(int i = 0; i < num_logs; i++) { |
||||
QGCLogEntry *entry = new QGCLogEntry(i); |
||||
_logEntriesModel.append(entry); |
||||
} |
||||
} |
||||
//-- Update this log record
|
||||
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; |
||||
} |
||||
//-- 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); |
||||
} |
||||
} |
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
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; |
||||
} |
@ -0,0 +1,193 @@
@@ -0,0 +1,193 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 |
Loading…
Reference in new issue