28 changed files with 1968 additions and 15 deletions
@ -0,0 +1,286 @@
@@ -0,0 +1,286 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 "MockMavlinkFileServer.h" |
||||
|
||||
const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = { |
||||
// File fits one Read Ack packet, partially filling data
|
||||
{ "partial.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) - 1 }, |
||||
// File fits one Read Ack packet, exactly filling all data
|
||||
{ "exact.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) }, |
||||
// File is larger than a single Read Ack packets, requires multiple Reads
|
||||
{ "multi.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) + 1 }, |
||||
}; |
||||
|
||||
// We only support a single fixed session
|
||||
const uint8_t MockMavlinkFileServer::_sessionId = 1; |
||||
|
||||
MockMavlinkFileServer::MockMavlinkFileServer(void) |
||||
{ |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
/// @brief Handles List command requests. Only supports root folder paths.
|
||||
/// File list returned is set using the setFileList method.
|
||||
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) |
||||
{ |
||||
// FIXME: Does not support directories that span multiple packets
|
||||
|
||||
QGCUASFileManager::Request ackResponse; |
||||
QString path; |
||||
|
||||
// We only support root path
|
||||
path = (char *)&request->data[0]; |
||||
if (!path.isEmpty() && path != "/") { |
||||
_sendNak(QGCUASFileManager::kErrNotDir); |
||||
return; |
||||
} |
||||
|
||||
// Offset requested is past the end of the list
|
||||
if (request->hdr.offset > (uint32_t)_fileList.size()) { |
||||
_sendNak(QGCUASFileManager::kErrEOF); |
||||
return; |
||||
} |
||||
|
||||
ackResponse.hdr.magic = 'f'; |
||||
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; |
||||
ackResponse.hdr.session = 0; |
||||
ackResponse.hdr.offset = request->hdr.offset; |
||||
ackResponse.hdr.size = 0; |
||||
|
||||
if (request->hdr.offset == 0) { |
||||
// Requesting first batch of file names
|
||||
Q_ASSERT(_fileList.size()); |
||||
char *bufPtr = (char *)&ackResponse.data[0]; |
||||
for (int i=0; i<_fileList.size(); i++) { |
||||
strcpy(bufPtr, _fileList[i].toStdString().c_str()); |
||||
size_t cchFilename = strlen(bufPtr); |
||||
Q_ASSERT(cchFilename); |
||||
ackResponse.hdr.size += cchFilename + 1; |
||||
bufPtr += cchFilename + 1; |
||||
} |
||||
|
||||
_emitResponse(&ackResponse); |
||||
} else { |
||||
// FIXME: Does not support directories that span multiple packets
|
||||
_sendNak(QGCUASFileManager::kErrEOF); |
||||
} |
||||
} |
||||
|
||||
/// @brief Handles Open command requests.
|
||||
void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request) |
||||
{ |
||||
QGCUASFileManager::Request response; |
||||
QString path; |
||||
|
||||
size_t cchPath = strnlen((char *)request->data, sizeof(request->data)); |
||||
Q_ASSERT(cchPath != sizeof(request->data)); |
||||
path = (char *)request->data; |
||||
|
||||
// Check path against one of our known test cases
|
||||
|
||||
bool found = false; |
||||
for (size_t i=0; i<cFileTestCases; i++) { |
||||
if (path == rgFileTestCases[i].filename) { |
||||
found = true; |
||||
_readFileLength = rgFileTestCases[i].length; |
||||
break; |
||||
} |
||||
} |
||||
if (!found) { |
||||
_sendNak(QGCUASFileManager::kErrNotFile); |
||||
return; |
||||
} |
||||
|
||||
response.hdr.magic = 'f'; |
||||
response.hdr.opcode = QGCUASFileManager::kRspAck; |
||||
response.hdr.session = _sessionId; |
||||
response.hdr.size = 0; |
||||
|
||||
_emitResponse(&response); |
||||
} |
||||
|
||||
/// @brief Handles Read command requests.
|
||||
void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request) |
||||
{ |
||||
QGCUASFileManager::Request response; |
||||
|
||||
if (request->hdr.session != _sessionId) { |
||||
_sendNak(QGCUASFileManager::kErrNoSession); |
||||
return; |
||||
} |
||||
|
||||
uint32_t readOffset = request->hdr.offset; // offset into file for reading
|
||||
uint8_t cDataBytes = 0; // current number of data bytes used
|
||||
|
||||
if (readOffset >= _readFileLength) { |
||||
_sendNak(QGCUASFileManager::kErrEOF); |
||||
return; |
||||
} |
||||
|
||||
// Write length byte if needed
|
||||
if (readOffset == 0) { |
||||
response.data[0] = _readFileLength; |
||||
readOffset++; |
||||
cDataBytes++; |
||||
} |
||||
|
||||
// Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
|
||||
for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) { |
||||
// Subtract one from readOffset to take into account length byte and start file data a 0x00
|
||||
response.data[cDataBytes] = (readOffset - 1) & 0xFF; |
||||
} |
||||
|
||||
// We should always have written something, otherwise there is something wrong with the code above
|
||||
Q_ASSERT(cDataBytes); |
||||
|
||||
response.hdr.magic = 'f'; |
||||
response.hdr.session = _sessionId; |
||||
response.hdr.size = cDataBytes; |
||||
response.hdr.offset = request->hdr.offset; |
||||
response.hdr.opcode = QGCUASFileManager::kRspAck; |
||||
|
||||
_emitResponse(&response); |
||||
} |
||||
|
||||
/// @brief Handles Terminate commands
|
||||
void MockMavlinkFileServer::_terminateCommand(QGCUASFileManager::Request* request) |
||||
{ |
||||
if (request->hdr.session != _sessionId) { |
||||
_sendNak(QGCUASFileManager::kErrNoSession); |
||||
return; |
||||
} |
||||
|
||||
_sendAck(); |
||||
|
||||
// Let our test harness know that we got a terminate command. This is used to validate the a Terminate is correctly
|
||||
// sent after an Open.
|
||||
emit terminateCommandReceived(); |
||||
} |
||||
|
||||
/// @brief Handles messages sent to the FTP server.
|
||||
void MockMavlinkFileServer::sendMessage(mavlink_message_t message) |
||||
{ |
||||
QGCUASFileManager::Request ackResponse; |
||||
|
||||
Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA); |
||||
|
||||
mavlink_encapsulated_data_t requestEncapsulatedData; |
||||
mavlink_msg_encapsulated_data_decode(&message, &requestEncapsulatedData); |
||||
QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestEncapsulatedData.data[0]; |
||||
|
||||
// Validate CRC
|
||||
if (request->hdr.crc32 != QGCUASFileManager::crc32(request)) { |
||||
_sendNak(QGCUASFileManager::kErrCrc); |
||||
} |
||||
|
||||
switch (request->hdr.opcode) { |
||||
case QGCUASFileManager::kCmdTestNoAck: |
||||
// ignored, ack not sent back, for testing only
|
||||
break; |
||||
|
||||
case QGCUASFileManager::kCmdReset: |
||||
// terminates all sessions
|
||||
// Fall through to send back Ack
|
||||
|
||||
case QGCUASFileManager::kCmdNone: |
||||
// ignored, always acked
|
||||
ackResponse.hdr.magic = 'f'; |
||||
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; |
||||
ackResponse.hdr.session = 0; |
||||
ackResponse.hdr.crc32 = 0; |
||||
ackResponse.hdr.size = 0; |
||||
_emitResponse(&ackResponse); |
||||
break; |
||||
|
||||
case QGCUASFileManager::kCmdList: |
||||
_listCommand(request); |
||||
break; |
||||
|
||||
case QGCUASFileManager::kCmdOpen: |
||||
_openCommand(request); |
||||
break; |
||||
|
||||
case QGCUASFileManager::kCmdRead: |
||||
_readCommand(request); |
||||
break; |
||||
|
||||
case QGCUASFileManager::kCmdTerminate: |
||||
_terminateCommand(request); |
||||
break; |
||||
|
||||
// Remainder of commands are NYI
|
||||
|
||||
case QGCUASFileManager::kCmdCreate: |
||||
// creates <path> for writing, returns <session>
|
||||
case QGCUASFileManager::kCmdWrite: |
||||
// appends <size> bytes at <offset> in <session>
|
||||
case QGCUASFileManager::kCmdRemove: |
||||
// remove file (only if created by server?)
|
||||
default: |
||||
// nack for all NYI opcodes
|
||||
_sendNak(QGCUASFileManager::kErrUnknownCommand); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/// @brief Sends an Ack
|
||||
void MockMavlinkFileServer::_sendAck(void) |
||||
{ |
||||
QGCUASFileManager::Request ackResponse; |
||||
|
||||
ackResponse.hdr.magic = 'f'; |
||||
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; |
||||
ackResponse.hdr.session = 0; |
||||
ackResponse.hdr.size = 0; |
||||
|
||||
_emitResponse(&ackResponse); |
||||
} |
||||
|
||||
/// @brief Sends a Nak with the specified error code.
|
||||
void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error) |
||||
{ |
||||
QGCUASFileManager::Request nakResponse; |
||||
|
||||
nakResponse.hdr.magic = 'f'; |
||||
nakResponse.hdr.opcode = QGCUASFileManager::kRspNak; |
||||
nakResponse.hdr.session = 0; |
||||
nakResponse.hdr.size = 1; |
||||
nakResponse.data[0] = error; |
||||
|
||||
_emitResponse(&nakResponse); |
||||
} |
||||
|
||||
/// @brief Emits a Request through the messageReceived signal.
|
||||
void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request) |
||||
{ |
||||
mavlink_message_t mavlinkMessage; |
||||
|
||||
request->hdr.crc32 = QGCUASFileManager::crc32(request); |
||||
|
||||
mavlink_msg_encapsulated_data_pack(250, 0, &mavlinkMessage, 0 /*_encdata_seq*/, (uint8_t*)request); |
||||
|
||||
emit messageReceived(NULL, mavlinkMessage); |
||||
} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 MOCKMAVLINKFILESERVER_H |
||||
#define MOCKMAVLINKFILESERVER_H |
||||
|
||||
#include "MockMavlinkInterface.h" |
||||
#include "QGCUASFileManager.h" |
||||
|
||||
/// @file
|
||||
/// @brief Mock implementation of Mavlink FTP server. Used as mavlink plugin to MockUAS.
|
||||
/// Only root directory access is supported.
|
||||
///
|
||||
/// @author Don Gagne <don@thegagnes.com>
|
||||
|
||||
#include <QStringList> |
||||
|
||||
class MockMavlinkFileServer : public MockMavlinkInterface |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
MockMavlinkFileServer(void); |
||||
|
||||
/// @brief Sets the list of files returned by the List command. Prepend names with F or D
|
||||
/// to indicate (F)ile or (D)irectory.
|
||||
void setFileList(QStringList& fileList) { _fileList = fileList; } |
||||
|
||||
// From MockMavlinkInterface
|
||||
virtual void sendMessage(mavlink_message_t message); |
||||
|
||||
struct FileTestCase { |
||||
const char* filename; |
||||
uint8_t length; |
||||
}; |
||||
|
||||
static const size_t cFileTestCases = 3; |
||||
static const FileTestCase rgFileTestCases[cFileTestCases]; |
||||
|
||||
signals: |
||||
void terminateCommandReceived(void); |
||||
|
||||
private: |
||||
void _sendAck(void); |
||||
void _sendNak(QGCUASFileManager::ErrorCode error); |
||||
void _emitResponse(QGCUASFileManager::Request* request); |
||||
void _listCommand(QGCUASFileManager::Request* request); |
||||
void _openCommand(QGCUASFileManager::Request* request); |
||||
void _readCommand(QGCUASFileManager::Request* request); |
||||
void _terminateCommand(QGCUASFileManager::Request* request); |
||||
|
||||
QStringList _fileList; ///< List of files returned by List command
|
||||
|
||||
static const uint8_t _sessionId; |
||||
uint8_t _readFileLength; ///< Length of active file being read
|
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// MockMavlinkInterface.cc
|
||||
// QGroundControl
|
||||
//
|
||||
// Created by Donald Gagne on 6/19/14.
|
||||
// Copyright (c) 2014 Donald Gagne. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MockMavlinkInterface.h" |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 <QObject> |
||||
|
||||
#include "QGCMAVLink.h" |
||||
#include "LinkInterface.h" |
||||
|
||||
#ifndef MOCKMAVLINKINTERFACE_H |
||||
#define MOCKMAVLINKINTERFACE_H |
||||
|
||||
class MockMavlinkInterface : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
virtual void sendMessage(mavlink_message_t message) = 0; |
||||
|
||||
signals: |
||||
// link argument will always be NULL
|
||||
void messageReceived(LinkInterface* link, mavlink_message_t message); |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,224 @@
@@ -0,0 +1,224 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 "QGCUASFileManagerTest.h" |
||||
|
||||
/// @file
|
||||
/// @brief QGCUASFileManager unit test. Note: All code here assumes all work between
|
||||
/// the unit test, mack mavlink file server and file manager is happening on
|
||||
/// the same thread.
|
||||
///
|
||||
/// @author Don Gagne <don@thegagnes.com>
|
||||
|
||||
QGCUASFileManagerUnitTest::QGCUASFileManagerUnitTest(void) : |
||||
_fileManager(NULL), |
||||
_multiSpy(NULL) |
||||
{ |
||||
|
||||
} |
||||
|
||||
// Called once before all test cases are run
|
||||
void QGCUASFileManagerUnitTest::initTestCase(void) |
||||
{ |
||||
_mockUAS.setMockMavlinkPlugin(&_mockFileServer); |
||||
} |
||||
|
||||
// Called before every test case
|
||||
void QGCUASFileManagerUnitTest::init(void) |
||||
{ |
||||
Q_ASSERT(_multiSpy == NULL); |
||||
|
||||
_fileManager = new QGCUASFileManager(NULL, &_mockUAS); |
||||
Q_CHECK_PTR(_fileManager); |
||||
|
||||
bool connected = connect(&_mockFileServer, SIGNAL(messageReceived(LinkInterface*, mavlink_message_t)), _fileManager, SLOT(receiveMessage(LinkInterface*, mavlink_message_t))); |
||||
Q_ASSERT(connected); |
||||
|
||||
connected = connect(_fileManager, SIGNAL(statusMessage(const QString&)), this, SLOT(statusMessage(const QString&))); |
||||
Q_ASSERT(connected); |
||||
|
||||
_rgSignals[statusMessageSignalIndex] = SIGNAL(statusMessage(const QString&)); |
||||
_rgSignals[errorMessageSignalIndex] = SIGNAL(errorMessage(const QString&)); |
||||
_rgSignals[resetStatusMessagesSignalIndex] = SIGNAL(resetStatusMessages(void)); |
||||
|
||||
_multiSpy = new MultiSignalSpy(); |
||||
Q_CHECK_PTR(_multiSpy); |
||||
QCOMPARE(_multiSpy->init(_fileManager, _rgSignals, _cSignals), true); |
||||
} |
||||
|
||||
// Called after every test case
|
||||
void QGCUASFileManagerUnitTest::cleanup(void) |
||||
{ |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_fileManager); |
||||
|
||||
delete _fileManager; |
||||
delete _multiSpy; |
||||
|
||||
_fileManager = NULL; |
||||
_multiSpy = NULL; |
||||
} |
||||
|
||||
/// @brief Connected to QGCUASFileManager statusMessage signal in order to catch list command output
|
||||
void QGCUASFileManagerUnitTest::statusMessage(const QString& msg) |
||||
{ |
||||
// Keep a list of all names received so we can test it for correctness
|
||||
_fileListReceived += msg; |
||||
} |
||||
|
||||
|
||||
void QGCUASFileManagerUnitTest::_ackTest(void) |
||||
{ |
||||
Q_ASSERT(_fileManager); |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_multiSpy->checkNoSignals() == true); |
||||
|
||||
// If the file manager doesn't receive an ack it will timeout and emit an error. So make sure
|
||||
// we don't get any error signals.
|
||||
QVERIFY(_fileManager->_sendCmdTestAck()); |
||||
QVERIFY(_multiSpy->checkNoSignals()); |
||||
} |
||||
|
||||
void QGCUASFileManagerUnitTest::_noAckTest(void) |
||||
{ |
||||
Q_ASSERT(_fileManager); |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_multiSpy->checkNoSignals() == true); |
||||
|
||||
// This should not get the ack back and timeout.
|
||||
QVERIFY(_fileManager->_sendCmdTestNoAck()); |
||||
QTest::qWait(2000); // Let the file manager timeout, magic number 2 secs must be larger than file manager ack timeout
|
||||
QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); |
||||
} |
||||
|
||||
void QGCUASFileManagerUnitTest::_resetTest(void) |
||||
{ |
||||
Q_ASSERT(_fileManager); |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_multiSpy->checkNoSignals() == true); |
||||
|
||||
// Send a reset command
|
||||
// We should not get any signals back from this
|
||||
QVERIFY(_fileManager->_sendCmdReset()); |
||||
QVERIFY(_multiSpy->checkNoSignals()); |
||||
} |
||||
|
||||
void QGCUASFileManagerUnitTest::_listTest(void) |
||||
{ |
||||
Q_ASSERT(_fileManager); |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_multiSpy->checkNoSignals() == true); |
||||
|
||||
// Send a bogus path
|
||||
// We should get a single resetStatusMessages signal
|
||||
// We should get a single errorMessage signal
|
||||
_fileManager->listDirectory("/bogus"); |
||||
QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask | resetStatusMessagesSignalMask), true); |
||||
_multiSpy->clearAllSignals(); |
||||
|
||||
// Send a list command at the root of the directory tree
|
||||
// We should get back a single resetStatusMessages signal
|
||||
// We should not get back an errorMessage signal
|
||||
// We should get back one or more statusMessage signals
|
||||
// The returned list should match out inputs
|
||||
|
||||
QStringList fileList; |
||||
fileList << "Ddir" << "Ffoo" << "Fbar"; |
||||
_mockFileServer.setFileList(fileList); |
||||
|
||||
QStringList fileListExpected; |
||||
fileListExpected << "dir/" << "foo" << "bar"; |
||||
|
||||
_fileListReceived.clear(); |
||||
|
||||
_fileManager->listDirectory("/"); |
||||
QCOMPARE(_multiSpy->checkSignalByMask(resetStatusMessagesSignalMask), true); // We should be told to reset status messages
|
||||
QCOMPARE(_multiSpy->checkNoSignalByMask(errorMessageSignalMask), true); // We should not get an error signals
|
||||
QVERIFY(_fileListReceived == fileListExpected); |
||||
} |
||||
|
||||
void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length) |
||||
{ |
||||
QFile file(filePath); |
||||
|
||||
// Make sure file size is correct
|
||||
QCOMPARE(file.size(), (qint64)length); |
||||
|
||||
// Read data
|
||||
QVERIFY(file.open(QIODevice::ReadOnly)); |
||||
QByteArray bytes = file.readAll(); |
||||
file.close(); |
||||
|
||||
// Validate length byte
|
||||
QCOMPARE((uint8_t)bytes[0], length); |
||||
|
||||
// Validate file contents:
|
||||
// Repeating 0x00, 0x01 .. 0xFF until file is full
|
||||
for (uint8_t i=1; i<bytes.length(); i++) { |
||||
QCOMPARE((uint8_t)bytes[i], (uint8_t)((i-1) & 0xFF)); |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileManagerUnitTest::_openTest(void) |
||||
{ |
||||
Q_ASSERT(_fileManager); |
||||
Q_ASSERT(_multiSpy); |
||||
Q_ASSERT(_multiSpy->checkNoSignals() == true); |
||||
|
||||
// Send a bogus path
|
||||
// We should get a single resetStatusMessages signal
|
||||
// We should get a single errorMessage signal
|
||||
_fileManager->downloadPath("bogus", QDir::temp()); |
||||
QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask | resetStatusMessagesSignalMask), true); |
||||
_multiSpy->clearAllSignals(); |
||||
|
||||
// Clean previous downloads
|
||||
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) { |
||||
QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename); |
||||
if (QFile::exists(filePath)) { |
||||
Q_ASSERT(QFile::remove(filePath)); |
||||
} |
||||
} |
||||
|
||||
// Run through the set of file test cases
|
||||
|
||||
// We setup a spy on the terminate command signal so that we can determine that a Terminate command was
|
||||
// correctly sent after the Open/Read commands complete.
|
||||
QSignalSpy terminateSpy(&_mockFileServer, SIGNAL(terminateCommandReceived())); |
||||
|
||||
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) { |
||||
_fileManager->downloadPath(MockMavlinkFileServer::rgFileTestCases[i].filename, QDir::temp()); |
||||
|
||||
// We should get a single resetStatusMessages signal
|
||||
// We should get a single statusMessage signal, which indicated download completion
|
||||
QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true); |
||||
_multiSpy->clearAllSignals(); |
||||
|
||||
// We should get a single Terminate command
|
||||
QCOMPARE(terminateSpy.count(), 1); |
||||
terminateSpy.clear(); |
||||
|
||||
QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename); |
||||
_validateFileContents(filePath, MockMavlinkFileServer::rgFileTestCases[i].length); |
||||
} |
||||
} |
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 QGCUASFILEMANAGERTEST_H |
||||
#define QGCUASFILEMANAGERTEST_H |
||||
|
||||
#include <QObject> |
||||
#include <QtTest/QtTest> |
||||
|
||||
#include "AutoTest.h" |
||||
#include "MockUAS.h" |
||||
#include "MockMavlinkFileServer.h" |
||||
#include "QGCUASFileManager.h" |
||||
#include "MultiSignalSpy.h" |
||||
|
||||
/// @file
|
||||
/// @brief QGCUASFileManager unit test
|
||||
///
|
||||
/// @author Don Gagne <don@thegagnes.com>
|
||||
|
||||
class QGCUASFileManagerUnitTest : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
QGCUASFileManagerUnitTest(void); |
||||
|
||||
private slots: |
||||
// Test case initialization
|
||||
void initTestCase(void); |
||||
void init(void); |
||||
void cleanup(void); |
||||
|
||||
// Test cases
|
||||
void _ackTest(void); |
||||
void _noAckTest(void); |
||||
void _resetTest(void); |
||||
void _listTest(void); |
||||
void _openTest(void); |
||||
|
||||
// Connected to QGCUASFileManager statusMessage signal
|
||||
void statusMessage(const QString&); |
||||
|
||||
private: |
||||
void _validateFileContents(const QString& filePath, uint8_t length); |
||||
|
||||
enum { |
||||
statusMessageSignalIndex = 0, |
||||
errorMessageSignalIndex, |
||||
resetStatusMessagesSignalIndex, |
||||
|
||||
maxSignalIndex |
||||
}; |
||||
|
||||
enum { |
||||
statusMessageSignalMask = 1 << statusMessageSignalIndex, |
||||
errorMessageSignalMask = 1 << errorMessageSignalIndex, |
||||
resetStatusMessagesSignalMask = 1 << resetStatusMessagesSignalIndex, |
||||
}; |
||||
|
||||
MockUAS _mockUAS; |
||||
MockMavlinkFileServer _mockFileServer; |
||||
|
||||
QGCUASFileManager* _fileManager; |
||||
|
||||
MultiSignalSpy* _multiSpy; |
||||
static const size_t _cSignals = maxSignalIndex; |
||||
const char* _rgSignals[_cSignals]; |
||||
|
||||
QStringList _fileListReceived; |
||||
}; |
||||
|
||||
DECLARE_TEST(QGCUASFileManagerUnitTest) |
||||
|
||||
#endif |
@ -0,0 +1,519 @@
@@ -0,0 +1,519 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 "QGCUASFileManager.h" |
||||
#include "QGC.h" |
||||
#include "MAVLinkProtocol.h" |
||||
|
||||
#include <QFile> |
||||
#include <QDir> |
||||
#include <string> |
||||
|
||||
static const quint32 crctab[] = |
||||
{ |
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, |
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, |
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, |
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, |
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, |
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, |
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, |
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, |
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, |
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, |
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, |
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, |
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, |
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, |
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, |
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, |
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, |
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, |
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, |
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, |
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, |
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, |
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, |
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, |
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, |
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, |
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, |
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, |
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, |
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, |
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, |
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d |
||||
}; |
||||
|
||||
|
||||
QGCUASFileManager::QGCUASFileManager(QObject* parent, UASInterface* uas) : |
||||
QObject(parent), |
||||
_currentOperation(kCOIdle), |
||||
_mav(uas), |
||||
_encdata_seq(0), |
||||
_activeSession(0) |
||||
{ |
||||
bool connected = connect(&_ackTimer, SIGNAL(timeout()), this, SLOT(_ackTimeout())); |
||||
Q_ASSERT(connected); |
||||
Q_UNUSED(connected); // Silence retail unused variable error
|
||||
} |
||||
|
||||
/// @brief Calculates a 32 bit CRC for the specified request.
|
||||
/// @param request Request to calculate CRC for. request->size must be set correctly.
|
||||
/// @param state previous crc state
|
||||
/// @return Calculated CRC
|
||||
quint32 QGCUASFileManager::crc32(Request* request, unsigned state) |
||||
{ |
||||
uint8_t* data = (uint8_t*)request; |
||||
size_t cbData = sizeof(RequestHeader) + request->hdr.size; |
||||
|
||||
// Always calculate CRC with 0 initial CRC value
|
||||
quint32 crcSave = request->hdr.crc32; |
||||
request->hdr.crc32 = 0; |
||||
|
||||
for (size_t i=0; i < cbData; i++) |
||||
state = crctab[(state ^ data[i]) & 0xff] ^ (state >> 8); |
||||
|
||||
request->hdr.crc32 = crcSave; |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/// @brief Respond to the Ack associated with the Open command with the next Read command.
|
||||
void QGCUASFileManager::_openAckResponse(Request* openAck) |
||||
{ |
||||
_currentOperation = kCORead; |
||||
_activeSession = openAck->hdr.session; |
||||
|
||||
_readOffset = 0; // Start reading at beginning of file
|
||||
_readFileAccumulator.clear(); // Start with an empty file
|
||||
|
||||
Request request; |
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = _activeSession; |
||||
request.hdr.opcode = kCmdRead; |
||||
request.hdr.offset = _readOffset; |
||||
request.hdr.size = sizeof(request.data); |
||||
|
||||
_sendRequest(&request); |
||||
} |
||||
|
||||
/// @brief Closes out a read session by writing the file and doing cleanup.
|
||||
/// @param success true: successful download completion, false: error during download
|
||||
void QGCUASFileManager::_closeReadSession(bool success) |
||||
{ |
||||
if (success) { |
||||
QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename); |
||||
|
||||
QFile file(downloadFilePath); |
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { |
||||
_emitErrorMessage(tr("Unable to open local file for writing (%1)").arg(downloadFilePath)); |
||||
return; |
||||
} |
||||
|
||||
qint64 bytesWritten = file.write((const char *)_readFileAccumulator, _readFileAccumulator.length()); |
||||
if (bytesWritten != _readFileAccumulator.length()) { |
||||
file.close(); |
||||
_emitErrorMessage(tr("Unable to write data to local file (%1)").arg(downloadFilePath)); |
||||
return; |
||||
} |
||||
file.close(); |
||||
|
||||
_emitStatusMessage(tr("Download complete '%1'").arg(downloadFilePath)); |
||||
} |
||||
|
||||
// Close the open session
|
||||
_sendTerminateCommand(); |
||||
} |
||||
|
||||
/// @brief Respond to the Ack associated with the Read command.
|
||||
void QGCUASFileManager::_readAckResponse(Request* readAck) |
||||
{ |
||||
if (readAck->hdr.session != _activeSession) { |
||||
_currentOperation = kCOIdle; |
||||
_readFileAccumulator.clear(); |
||||
_emitErrorMessage(tr("Read: Incorrect session returned")); |
||||
return; |
||||
} |
||||
|
||||
if (readAck->hdr.offset != _readOffset) { |
||||
_currentOperation = kCOIdle; |
||||
_readFileAccumulator.clear(); |
||||
_emitErrorMessage(tr("Read: Offset returned (%1) differs from offset requested (%2)").arg(readAck->hdr.offset).arg(_readOffset)); |
||||
return; |
||||
} |
||||
|
||||
_readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size); |
||||
|
||||
if (readAck->hdr.size == sizeof(readAck->data)) { |
||||
// Possibly still more data to read, send next read request
|
||||
|
||||
_currentOperation = kCORead; |
||||
|
||||
_readOffset += readAck->hdr.size; |
||||
|
||||
Request request; |
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = _activeSession; |
||||
request.hdr.opcode = kCmdRead; |
||||
request.hdr.offset = _readOffset; |
||||
|
||||
_sendRequest(&request); |
||||
} else { |
||||
// We only receieved a partial buffer back. These means we are at EOF
|
||||
_currentOperation = kCOIdle; |
||||
_closeReadSession(true /* success */); |
||||
} |
||||
} |
||||
|
||||
/// @brief Respond to the Ack associated with the List command.
|
||||
void QGCUASFileManager::_listAckResponse(Request* listAck) |
||||
{ |
||||
if (listAck->hdr.offset != _listOffset) { |
||||
_currentOperation = kCOIdle; |
||||
_emitErrorMessage(tr("List: Offset returned (%1) differs from offset requested (%2)").arg(listAck->hdr.offset).arg(_listOffset)); |
||||
return; |
||||
} |
||||
|
||||
uint8_t offset = 0; |
||||
uint8_t cListEntries = 0; |
||||
uint8_t cBytes = listAck->hdr.size; |
||||
|
||||
// parse filenames out of the buffer
|
||||
while (offset < cBytes) { |
||||
const char * ptr = ((const char *)listAck->data) + offset; |
||||
|
||||
// get the length of the name
|
||||
uint8_t cBytesLeft = cBytes - offset; |
||||
size_t nlen = strnlen(ptr, cBytesLeft); |
||||
if (nlen < 2) { |
||||
_currentOperation = kCOIdle; |
||||
_emitErrorMessage(tr("Incorrectly formed list entry: '%1'").arg(ptr)); |
||||
return; |
||||
} else if (nlen == cBytesLeft) { |
||||
_currentOperation = kCOIdle; |
||||
_emitErrorMessage(tr("Missing NULL termination in list entry")); |
||||
return; |
||||
} |
||||
|
||||
// Returned names are prepended with D for directory, F for file, U for unknown
|
||||
if (*ptr == 'F' || *ptr == 'D') { |
||||
// put it in the view
|
||||
_emitStatusMessage(ptr); |
||||
} |
||||
|
||||
// account for the name + NUL
|
||||
offset += nlen + 1; |
||||
|
||||
cListEntries++; |
||||
} |
||||
|
||||
if (listAck->hdr.size == 0) { |
||||
// Directory is empty, we're done
|
||||
Q_ASSERT(listAck->hdr.opcode == kRspAck); |
||||
_currentOperation = kCOIdle; |
||||
emit listComplete(); |
||||
} else { |
||||
// Possibly more entries to come, need to keep trying till we get EOF
|
||||
_currentOperation = kCOList; |
||||
_listOffset += cListEntries; |
||||
_sendListCommand(); |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t message) |
||||
{ |
||||
Q_UNUSED(link); |
||||
|
||||
if (message.msgid != MAVLINK_MSG_ID_ENCAPSULATED_DATA) { |
||||
// wtf, not for us
|
||||
return; |
||||
} |
||||
|
||||
_clearAckTimeout(); |
||||
|
||||
mavlink_encapsulated_data_t data; |
||||
mavlink_msg_encapsulated_data_decode(&message, &data); |
||||
Request* request = (Request*)&data.data[0]; |
||||
|
||||
// FIXME: Check CRC
|
||||
|
||||
if (request->hdr.opcode == kRspAck) { |
||||
|
||||
switch (_currentOperation) { |
||||
case kCOIdle: |
||||
// we should not be seeing anything here.. shut the other guy up
|
||||
_sendCmdReset(); |
||||
break; |
||||
|
||||
case kCOAck: |
||||
// We are expecting an ack back
|
||||
_currentOperation = kCOIdle; |
||||
break; |
||||
|
||||
case kCOList: |
||||
_listAckResponse(request); |
||||
break; |
||||
|
||||
case kCOOpen: |
||||
_openAckResponse(request); |
||||
break; |
||||
|
||||
case kCORead: |
||||
_readAckResponse(request); |
||||
break; |
||||
|
||||
default: |
||||
_emitErrorMessage("Ack received in unexpected state"); |
||||
break; |
||||
} |
||||
} else if (request->hdr.opcode == kRspNak) { |
||||
Q_ASSERT(request->hdr.size == 1); // Should only have one byte of error code
|
||||
|
||||
OperationState previousOperation = _currentOperation; |
||||
uint8_t errorCode = request->data[0]; |
||||
|
||||
_currentOperation = kCOIdle; |
||||
|
||||
if (previousOperation == kCOList && errorCode == kErrEOF) { |
||||
// This is not an error, just the end of the read loop
|
||||
emit listComplete(); |
||||
return; |
||||
} else if (previousOperation == kCORead && errorCode == kErrEOF) { |
||||
// This is not an error, just the end of the read loop
|
||||
_closeReadSession(true /* success */); |
||||
return; |
||||
} else { |
||||
// Generic Nak handling
|
||||
if (previousOperation == kCORead) { |
||||
// Nak error during read loop, download failed
|
||||
_closeReadSession(false /* failure */); |
||||
} |
||||
_emitErrorMessage(tr("Nak received, error: %1").arg(errorString(request->data[0]))); |
||||
} |
||||
} else { |
||||
// Note that we don't change our operation state. If something goes wrong beyond this, the operation
|
||||
// will time out.
|
||||
_emitErrorMessage(tr("Unknown opcode returned from server: %1").arg(request->hdr.opcode)); |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileManager::listDirectory(const QString& dirPath) |
||||
{ |
||||
if (_currentOperation != kCOIdle) { |
||||
_emitErrorMessage(tr("Command not sent. Waiting for previous command to complete.")); |
||||
return; |
||||
} |
||||
|
||||
// clear the text widget
|
||||
emit resetStatusMessages(); |
||||
|
||||
// initialise the lister
|
||||
_listPath = dirPath; |
||||
_listOffset = 0; |
||||
_currentOperation = kCOList; |
||||
|
||||
// and send the initial request
|
||||
_sendListCommand(); |
||||
} |
||||
|
||||
void QGCUASFileManager::_fillRequestWithString(Request* request, const QString& str) |
||||
{ |
||||
strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data)); |
||||
request->hdr.size = strnlen((const char *)&request->data[0], sizeof(request->data)); |
||||
} |
||||
|
||||
void QGCUASFileManager::_sendListCommand(void) |
||||
{ |
||||
Request request; |
||||
|
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = 0; |
||||
request.hdr.opcode = kCmdList; |
||||
request.hdr.offset = _listOffset; |
||||
|
||||
_fillRequestWithString(&request, _listPath); |
||||
|
||||
_sendRequest(&request); |
||||
} |
||||
|
||||
/// @brief Downloads the specified file.
|
||||
/// @param from File to download from UAS, fully qualified path
|
||||
/// @param downloadDir Local directory to download file to
|
||||
void QGCUASFileManager::downloadPath(const QString& from, const QDir& downloadDir) |
||||
{ |
||||
if (from.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
_readFileDownloadDir.setPath(downloadDir.absolutePath()); |
||||
|
||||
// We need to strip off the file name from the fully qualified path. We can't use the usual QDir
|
||||
// routines because this path does not exist locally.
|
||||
int i; |
||||
for (i=from.size()-1; i>=0; i--) { |
||||
if (from[i] == '/') { |
||||
break; |
||||
} |
||||
} |
||||
i++; // move past slash
|
||||
_readFileDownloadFilename = from.right(from.size() - i); |
||||
|
||||
emit resetStatusMessages(); |
||||
|
||||
_currentOperation = kCOOpen; |
||||
|
||||
Request request; |
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = 0; |
||||
request.hdr.opcode = kCmdOpen; |
||||
request.hdr.offset = 0; |
||||
_fillRequestWithString(&request, from); |
||||
_sendRequest(&request); |
||||
} |
||||
|
||||
QString QGCUASFileManager::errorString(uint8_t errorCode) |
||||
{ |
||||
switch(errorCode) { |
||||
case kErrNone: |
||||
return QString("no error"); |
||||
case kErrNoRequest: |
||||
return QString("bad request"); |
||||
case kErrNoSession: |
||||
return QString("bad session"); |
||||
case kErrSequence: |
||||
return QString("bad sequence number"); |
||||
case kErrNotDir: |
||||
return QString("not a directory"); |
||||
case kErrNotFile: |
||||
return QString("not a file"); |
||||
case kErrEOF: |
||||
return QString("read beyond end of file"); |
||||
case kErrNotAppend: |
||||
return QString("write not at end of file"); |
||||
case kErrTooBig: |
||||
return QString("file too big"); |
||||
case kErrIO: |
||||
return QString("device I/O error"); |
||||
case kErrPerm: |
||||
return QString("permission denied"); |
||||
case kErrUnknownCommand: |
||||
return QString("unknown command"); |
||||
case kErrCrc: |
||||
return QString("bad crc"); |
||||
default: |
||||
return QString("unknown error code"); |
||||
} |
||||
} |
||||
|
||||
/// @brief Sends a command which only requires an opcode and no additional data
|
||||
/// @param opcode Opcode to send
|
||||
/// @param newOpState State to put state machine into
|
||||
/// @return TRUE: command sent, FALSE: command not sent, waiting for previous command to finish
|
||||
bool QGCUASFileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState) |
||||
{ |
||||
if (_currentOperation != kCOIdle) { |
||||
// Can't have multiple commands in play at the same time
|
||||
return false; |
||||
} |
||||
|
||||
Request request; |
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = 0; |
||||
request.hdr.opcode = opcode; |
||||
request.hdr.offset = 0; |
||||
request.hdr.size = 0; |
||||
|
||||
_currentOperation = newOpState; |
||||
|
||||
_sendRequest(&request); |
||||
|
||||
return TRUE; |
||||
} |
||||
|
||||
/// @brief Starts the ack timeout timer
|
||||
void QGCUASFileManager::_setupAckTimeout(void) |
||||
{ |
||||
Q_ASSERT(!_ackTimer.isActive()); |
||||
|
||||
_ackTimer.setSingleShot(true); |
||||
_ackTimer.start(_ackTimerTimeoutMsecs); |
||||
} |
||||
|
||||
/// @brief Clears the ack timeout timer
|
||||
void QGCUASFileManager::_clearAckTimeout(void) |
||||
{ |
||||
Q_ASSERT(_ackTimer.isActive()); |
||||
|
||||
_ackTimer.stop(); |
||||
} |
||||
|
||||
/// @brief Called when ack timeout timer fires
|
||||
void QGCUASFileManager::_ackTimeout(void) |
||||
{ |
||||
_emitErrorMessage(tr("Timeout waiting for ack")); |
||||
|
||||
switch (_currentOperation) { |
||||
case kCORead: |
||||
_currentOperation = kCOAck; |
||||
_sendTerminateCommand(); |
||||
break; |
||||
default: |
||||
_currentOperation = kCOIdle; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileManager::_sendTerminateCommand(void) |
||||
{ |
||||
Request request; |
||||
request.hdr.magic = 'f'; |
||||
request.hdr.session = _activeSession; |
||||
request.hdr.opcode = kCmdTerminate; |
||||
_sendRequest(&request); |
||||
} |
||||
|
||||
void QGCUASFileManager::_emitErrorMessage(const QString& msg) |
||||
{ |
||||
qDebug() << "QGCUASFileManager: Error" << msg; |
||||
emit errorMessage(msg); |
||||
} |
||||
|
||||
void QGCUASFileManager::_emitStatusMessage(const QString& msg) |
||||
{ |
||||
qDebug() << "QGCUASFileManager: Status" << msg; |
||||
emit statusMessage(msg); |
||||
} |
||||
|
||||
/// @brief Sends the specified Request out to the UAS.
|
||||
void QGCUASFileManager::_sendRequest(Request* request) |
||||
{ |
||||
mavlink_message_t message; |
||||
|
||||
_setupAckTimeout(); |
||||
|
||||
request->hdr.crc32 = crc32(request); |
||||
// FIXME: Send correct system id instead of harcoded 250
|
||||
mavlink_msg_encapsulated_data_pack(250, 0, &message, _encdata_seq, (uint8_t*)request); |
||||
_mav->sendMessage(message); |
||||
} |
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 QGCUASFILEMANAGER_H |
||||
#define QGCUASFILEMANAGER_H |
||||
|
||||
#include <QObject> |
||||
#include <QDir> |
||||
|
||||
#include "UASInterface.h" |
||||
|
||||
class QGCUASFileManager : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
QGCUASFileManager(QObject* parent, UASInterface* uas); |
||||
|
||||
/// These methods are only used for testing purposes.
|
||||
bool _sendCmdTestAck(void) { return _sendOpcodeOnlyCmd(kCmdNone, kCOAck); }; |
||||
bool _sendCmdTestNoAck(void) { return _sendOpcodeOnlyCmd(kCmdTestNoAck, kCOAck); }; |
||||
bool _sendCmdReset(void) { return _sendOpcodeOnlyCmd(kCmdReset, kCOAck); }; |
||||
|
||||
signals: |
||||
void statusMessage(const QString& msg); |
||||
void resetStatusMessages(); |
||||
void errorMessage(const QString& msg); |
||||
void listComplete(void); |
||||
|
||||
public slots: |
||||
void receiveMessage(LinkInterface* link, mavlink_message_t message); |
||||
void listDirectory(const QString& dirPath); |
||||
void downloadPath(const QString& from, const QDir& downloadDir); |
||||
|
||||
protected: |
||||
struct RequestHeader |
||||
{ |
||||
uint8_t magic; ///> Magic byte 'f' to idenitfy FTP protocol
|
||||
uint8_t session; ///> Session id for read and write commands
|
||||
uint8_t opcode; ///> Command opcode
|
||||
uint8_t size; ///> Size of data
|
||||
uint32_t crc32; ///> CRC for entire Request structure, with crc32 set to 0
|
||||
uint32_t offset; ///> Offsets for List and Read commands
|
||||
}; |
||||
|
||||
struct Request |
||||
{ |
||||
struct RequestHeader hdr; |
||||
// The entire Request must fit into the data member of the mavlink_encapsulated_data_t structure. We use as many leftover bytes
|
||||
// after we use up space for the RequestHeader for the data portion of the Request.
|
||||
uint8_t data[sizeof(((mavlink_encapsulated_data_t*)0)->data) - sizeof(RequestHeader)]; |
||||
}; |
||||
|
||||
enum Opcode |
||||
{ |
||||
// Commands
|
||||
kCmdNone, ///> ignored, always acked
|
||||
kCmdTerminate, ///> releases sessionID, closes file
|
||||
kCmdReset, ///> terminates all sessions
|
||||
kCmdList, ///> list files in <path> from <offset>
|
||||
kCmdOpen, ///> opens <path> for reading, returns <session>
|
||||
kCmdRead, ///> reads <size> bytes from <offset> in <session>
|
||||
kCmdCreate, ///> creates <path> for writing, returns <session>
|
||||
kCmdWrite, ///> appends <size> bytes at <offset> in <session>
|
||||
kCmdRemove, ///> remove file (only if created by server?)
|
||||
|
||||
// Responses
|
||||
kRspAck, ///> positive acknowledgement of previous command
|
||||
kRspNak, ///> negative acknowledgement of previous command
|
||||
|
||||
// Used for testing only, not part of protocol
|
||||
kCmdTestNoAck, // ignored, ack not sent back, should timeout waiting for ack
|
||||
}; |
||||
|
||||
enum ErrorCode |
||||
{ |
||||
kErrNone, |
||||
kErrNoRequest, |
||||
kErrNoSession, |
||||
kErrSequence, |
||||
kErrNotDir, |
||||
kErrNotFile, |
||||
kErrEOF, |
||||
kErrNotAppend, |
||||
kErrTooBig, |
||||
kErrIO, |
||||
kErrPerm, |
||||
kErrUnknownCommand, |
||||
kErrCrc |
||||
}; |
||||
|
||||
|
||||
enum OperationState |
||||
{ |
||||
kCOIdle, // not doing anything
|
||||
kCOAck, // waiting for an Ack
|
||||
kCOList, // waiting for List response
|
||||
kCOOpen, // waiting for Open response
|
||||
kCORead, // waiting for Read response
|
||||
}; |
||||
|
||||
|
||||
protected slots: |
||||
void _ackTimeout(void); |
||||
|
||||
protected: |
||||
bool _sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState); |
||||
void _setupAckTimeout(void); |
||||
void _clearAckTimeout(void); |
||||
void _emitErrorMessage(const QString& msg); |
||||
void _emitStatusMessage(const QString& msg); |
||||
void _sendRequest(Request* request); |
||||
void _fillRequestWithString(Request* request, const QString& str); |
||||
void _openAckResponse(Request* openAck); |
||||
void _readAckResponse(Request* readAck); |
||||
void _listAckResponse(Request* listAck); |
||||
void _sendListCommand(void); |
||||
void _sendTerminateCommand(void); |
||||
void _closeReadSession(bool success); |
||||
|
||||
static quint32 crc32(Request* request, unsigned state = 0); |
||||
static QString errorString(uint8_t errorCode); |
||||
|
||||
OperationState _currentOperation; ///> Current operation of state machine
|
||||
QTimer _ackTimer; ///> Used to signal a timeout waiting for an ack
|
||||
static const int _ackTimerTimeoutMsecs = 1000; ///> Timeout in msecs for ack timer
|
||||
|
||||
UASInterface* _mav; |
||||
quint16 _encdata_seq; |
||||
|
||||
unsigned _listOffset; ///> offset for the current List operation
|
||||
QString _listPath; ///> path for the current List operation
|
||||
|
||||
uint8_t _activeSession; ///> currently active session, 0 for none
|
||||
uint32_t _readOffset; ///> current read offset
|
||||
QByteArray _readFileAccumulator; ///> Holds file being downloaded
|
||||
QDir _readFileDownloadDir; ///> Directory to download file to
|
||||
QString _readFileDownloadFilename; ///> Filename (no path) for download file
|
||||
|
||||
// We give MockMavlinkFileServer friend access so that it can use the data structures and opcodes
|
||||
// to build a mock mavlink file server for testing.
|
||||
friend class MockMavlinkFileServer; |
||||
}; |
||||
|
||||
#endif // QGCUASFILEMANAGER_H
|
@ -0,0 +1,199 @@
@@ -0,0 +1,199 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 "QGCUASFileView.h" |
||||
#include "uas/QGCUASFileManager.h" |
||||
|
||||
#include <QFileDialog> |
||||
#include <QDir> |
||||
#include <QMessageBox> |
||||
|
||||
QGCUASFileView::QGCUASFileView(QWidget *parent, QGCUASFileManager *manager) : |
||||
QWidget(parent), |
||||
_manager(manager) |
||||
{ |
||||
_ui.setupUi(this); |
||||
|
||||
bool success = connect(_ui.listFilesButton, SIGNAL(clicked()), this, SLOT(_refreshTree())); |
||||
Q_ASSERT(success); |
||||
success = connect(_ui.downloadButton, SIGNAL(clicked()), this, SLOT(_downloadFiles())); |
||||
Q_ASSERT(success); |
||||
success = connect(_ui.treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(_currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*))); |
||||
Q_ASSERT(success); |
||||
Q_UNUSED(success); |
||||
} |
||||
|
||||
void QGCUASFileView::_downloadFiles(void) |
||||
{ |
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Download Directory"), |
||||
QDir::homePath(), |
||||
QFileDialog::ShowDirsOnly |
||||
| QFileDialog::DontResolveSymlinks); |
||||
// And now download to this location
|
||||
QString path; |
||||
QTreeWidgetItem* item = _ui.treeWidget->currentItem(); |
||||
if (item && item->type() == _typeFile) { |
||||
do { |
||||
path.prepend("/" + item->text(0)); |
||||
item = item->parent(); |
||||
} while (item); |
||||
qDebug() << "Download: " << path; |
||||
|
||||
bool success = connect(_manager, SIGNAL(statusMessage(QString)), this, SLOT(_downloadStatusMessage(QString))); |
||||
Q_ASSERT(success); |
||||
success = connect(_manager, SIGNAL(errorMessage(QString)), this, SLOT(_downloadStatusMessage(QString))); |
||||
Q_ASSERT(success); |
||||
Q_UNUSED(success); |
||||
_manager->downloadPath(path, QDir(dir)); |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileView::_refreshTree(void) |
||||
{ |
||||
QTreeWidgetItem* item; |
||||
|
||||
for (int i=_ui.treeWidget->invisibleRootItem()->childCount(); i>=0; i--) { |
||||
item = _ui.treeWidget->takeTopLevelItem(i); |
||||
delete item; |
||||
} |
||||
|
||||
_walkIndexStack.clear(); |
||||
_walkItemStack.clear(); |
||||
_walkIndexStack.append(0); |
||||
_walkItemStack.append(_ui.treeWidget->invisibleRootItem()); |
||||
|
||||
bool success = connect(_manager, SIGNAL(statusMessage(QString)), this, SLOT(_treeStatusMessage(QString))); |
||||
Q_ASSERT(success); |
||||
success = connect(_manager, SIGNAL(errorMessage(QString)), this, SLOT(_treeErrorMessage(QString))); |
||||
Q_ASSERT(success); |
||||
success = connect(_manager, SIGNAL(listComplete(void)), this, SLOT(_listComplete(void))); |
||||
Q_ASSERT(success); |
||||
Q_UNUSED(success); |
||||
|
||||
qDebug() << "List: /"; |
||||
_manager->listDirectory("/"); |
||||
} |
||||
|
||||
void QGCUASFileView::_treeStatusMessage(const QString& msg) |
||||
{ |
||||
int type; |
||||
if (msg.startsWith("F")) { |
||||
type = _typeFile; |
||||
} else if (msg.startsWith("D")) { |
||||
type = _typeDir; |
||||
if (msg == "D." || msg == "D..") { |
||||
return; |
||||
} |
||||
} else { |
||||
Q_ASSERT(false); |
||||
} |
||||
|
||||
QTreeWidgetItem* item; |
||||
if (_walkItemStack.count() == 0) { |
||||
item = new QTreeWidgetItem(_ui.treeWidget, type); |
||||
} else { |
||||
item = new QTreeWidgetItem(_walkItemStack.last(), type); |
||||
} |
||||
Q_CHECK_PTR(item); |
||||
|
||||
item->setText(0, msg.right(msg.size() - 1)); |
||||
} |
||||
|
||||
void QGCUASFileView::_treeErrorMessage(const QString& msg) |
||||
{ |
||||
QTreeWidgetItem* item; |
||||
if (_walkItemStack.count() == 0) { |
||||
item = new QTreeWidgetItem(_ui.treeWidget, _typeError); |
||||
} else { |
||||
item = new QTreeWidgetItem(_walkItemStack.last(), _typeError); |
||||
} |
||||
Q_CHECK_PTR(item); |
||||
|
||||
item->setText(0, tr("Error: ") + msg); |
||||
} |
||||
|
||||
void QGCUASFileView::_listComplete(void) |
||||
{ |
||||
// Walk the current items, traversing down into directories
|
||||
|
||||
Again: |
||||
int walkIndex = _walkIndexStack.last(); |
||||
QTreeWidgetItem* parentItem = _walkItemStack.last(); |
||||
QTreeWidgetItem* childItem = parentItem->child(walkIndex); |
||||
|
||||
// Loop until we hit a directory
|
||||
while (childItem && childItem->type() != _typeDir) { |
||||
// Move to next index at current level
|
||||
_walkIndexStack.last() = ++walkIndex; |
||||
childItem = parentItem->child(walkIndex); |
||||
} |
||||
|
||||
if (childItem) { |
||||
// Process this item
|
||||
QString text = childItem->text(0); |
||||
|
||||
// Move to the next item for processing at this level
|
||||
_walkIndexStack.last() = ++walkIndex; |
||||
|
||||
// Push this new directory on the stack
|
||||
_walkItemStack.append(childItem); |
||||
_walkIndexStack.append(0); |
||||
|
||||
// Ask for the directory list
|
||||
QString dir; |
||||
for (int i=1; i<_walkItemStack.count(); i++) { |
||||
QTreeWidgetItem* item = _walkItemStack[i]; |
||||
dir.append("/" + item->text(0)); |
||||
} |
||||
qDebug() << "List:" << dir; |
||||
_manager->listDirectory(dir); |
||||
} else { |
||||
// We have run out of items at the this level, pop the stack and keep going at that level
|
||||
_walkIndexStack.removeLast(); |
||||
_walkItemStack.removeLast(); |
||||
if (_walkIndexStack.count() != 0) { |
||||
goto Again; |
||||
} else { |
||||
disconnect(_manager, SIGNAL(statusMessage(QString)), this, SLOT(_treeStatusMessage(QString))); |
||||
disconnect(_manager, SIGNAL(errorMessage(QString)), this, SLOT(_treeErrorMessage(QString))); |
||||
disconnect(_manager, SIGNAL(listComplete(void)), this, SLOT(_listComplete(void))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileView::_downloadStatusMessage(const QString& msg) |
||||
{ |
||||
disconnect(_manager, SIGNAL(statusMessage(QString)), this, SLOT(_downloadStatusMessage(QString))); |
||||
disconnect(_manager, SIGNAL(errorMessage(QString)), this, SLOT(_downloadStatusMessage(QString))); |
||||
|
||||
QMessageBox msgBox; |
||||
msgBox.setWindowModality(Qt::ApplicationModal); |
||||
msgBox.setText(msg); |
||||
msgBox.exec(); |
||||
} |
||||
|
||||
void QGCUASFileView::_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) |
||||
{ |
||||
Q_UNUSED(previous); |
||||
_ui.downloadButton->setEnabled(current->type() == _typeFile); |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/*=====================================================================
|
||||
|
||||
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 QGCUASFILEVIEW_H |
||||
#define QGCUASFILEVIEW_H |
||||
|
||||
#include <QWidget> |
||||
#include <QTreeWidgetItem> |
||||
|
||||
#include "uas/QGCUASFileManager.h" |
||||
#include "ui_QGCUASFileView.h" |
||||
|
||||
class QGCUASFileView : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit QGCUASFileView(QWidget *parent, QGCUASFileManager *manager); |
||||
|
||||
protected: |
||||
QGCUASFileManager* _manager; |
||||
|
||||
private slots: |
||||
void _refreshTree(void); |
||||
void _downloadFiles(void); |
||||
void _treeStatusMessage(const QString& msg); |
||||
void _treeErrorMessage(const QString& msg); |
||||
void _listComplete(void); |
||||
void _downloadStatusMessage(const QString& msg); |
||||
void _currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); |
||||
|
||||
private: |
||||
static const int _typeFile = QTreeWidgetItem::UserType + 1; |
||||
static const int _typeDir = QTreeWidgetItem::UserType + 2; |
||||
static const int _typeError = QTreeWidgetItem::UserType + 3; |
||||
|
||||
QList<int> _walkIndexStack; |
||||
QList<QTreeWidgetItem*> _walkItemStack; |
||||
Ui::QGCUASFileView _ui; |
||||
}; |
||||
|
||||
#endif // QGCUASFILEVIEW_H
|
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>QGCUASFileView</class> |
||||
<widget class="QWidget" name="QGCUASFileView"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>414</width> |
||||
<height>518</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Form</string> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout"> |
||||
<item row="1" column="1"> |
||||
<widget class="QPushButton" name="listFilesButton"> |
||||
<property name="text"> |
||||
<string>List Files</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="0" colspan="3"> |
||||
<widget class="QTreeWidget" name="treeWidget"> |
||||
<property name="headerHidden"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<column> |
||||
<property name="text"> |
||||
<string notr="true">1</string> |
||||
</property> |
||||
</column> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="2"> |
||||
<widget class="QPushButton" name="downloadButton"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Download File</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
#include "QGCUASFileViewMulti.h" |
||||
#include "ui_QGCUASFileViewMulti.h" |
||||
#include "UASInterface.h" |
||||
#include "UASManager.h" |
||||
#include "QGCUASFileView.h" |
||||
|
||||
QGCUASFileViewMulti::QGCUASFileViewMulti(QWidget *parent) : |
||||
QWidget(parent), |
||||
ui(new Ui::QGCUASFileViewMulti) |
||||
{ |
||||
ui->setupUi(this); |
||||
setMinimumSize(600, 80); |
||||
connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(systemCreated(UASInterface*))); |
||||
connect(UASManager::instance(), SIGNAL(activeUASSet(int)), this, SLOT(systemSetActive(int))); |
||||
|
||||
if (UASManager::instance()->getActiveUAS()) { |
||||
systemCreated(UASManager::instance()->getActiveUAS()); |
||||
systemSetActive(UASManager::instance()->getActiveUAS()->getUASID()); |
||||
} |
||||
|
||||
} |
||||
|
||||
void QGCUASFileViewMulti::systemDeleted(QObject* uas) |
||||
{ |
||||
UASInterface* mav = dynamic_cast<UASInterface*>(uas); |
||||
if (mav) |
||||
{ |
||||
int id = mav->getUASID(); |
||||
QGCUASFileView* list = lists.value(id, NULL); |
||||
if (list) |
||||
{ |
||||
delete list; |
||||
lists.remove(id); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void QGCUASFileViewMulti::systemCreated(UASInterface* uas) |
||||
{ |
||||
if (!uas) { |
||||
return; |
||||
} |
||||
|
||||
QGCUASFileView* list = new QGCUASFileView(ui->stackedWidget, uas->getFileManager()); |
||||
lists.insert(uas->getUASID(), list); |
||||
ui->stackedWidget->addWidget(list); |
||||
// Ensure widget is deleted when system is deleted
|
||||
connect(uas, SIGNAL(destroyed(QObject*)), this, SLOT(systemDeleted(QObject*))); |
||||
} |
||||
|
||||
void QGCUASFileViewMulti::systemSetActive(int uas) |
||||
{ |
||||
QGCUASFileView* list = lists.value(uas, NULL); |
||||
if (list) { |
||||
ui->stackedWidget->setCurrentWidget(list); |
||||
} |
||||
} |
||||
|
||||
QGCUASFileViewMulti::~QGCUASFileViewMulti() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
void QGCUASFileViewMulti::changeEvent(QEvent *e) |
||||
{ |
||||
QWidget::changeEvent(e); |
||||
switch (e->type()) { |
||||
case QEvent::LanguageChange: |
||||
ui->retranslateUi(this); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
#ifndef QGCUASFILEVIEWMULTI_H |
||||
#define QGCUASFILEVIEWMULTI_H |
||||
|
||||
#include <QWidget> |
||||
#include <QMap> |
||||
|
||||
#include "QGCUASFileView.h" |
||||
#include "UASInterface.h" |
||||
|
||||
namespace Ui |
||||
{ |
||||
class QGCUASFileViewMulti; |
||||
} |
||||
|
||||
class QGCUASFileViewMulti : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit QGCUASFileViewMulti(QWidget *parent = 0); |
||||
~QGCUASFileViewMulti(); |
||||
|
||||
public slots: |
||||
void systemDeleted(QObject* uas); |
||||
void systemCreated(UASInterface* uas); |
||||
void systemSetActive(int uas); |
||||
|
||||
protected: |
||||
void changeEvent(QEvent *e); |
||||
QMap<int, QGCUASFileView*> lists; |
||||
|
||||
private: |
||||
Ui::QGCUASFileViewMulti *ui; |
||||
}; |
||||
|
||||
#endif // QGCUASFILEVIEWMULTI_H
|
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>QGCUASFileViewMulti</class> |
||||
<widget class="QWidget" name="QGCUASFileViewMulti"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>400</width> |
||||
<height>300</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Form</string> |
||||
</property> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<property name="margin"> |
||||
<number>0</number> |
||||
</property> |
||||
<item> |
||||
<widget class="QStackedWidget" name="stackedWidget"/> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
Loading…
Reference in new issue