diff --git a/libs/mavlink/include/mavlink/v1.0/checksum.h b/libs/mavlink/include/mavlink/v1.0/checksum.h index 948e080a14a438f96d4b3e7199bf1e2ab86d0298..7d9b6ac0c7a94499b6b303c0f691ccfff3a013f5 100644 --- a/libs/mavlink/include/mavlink/v1.0/checksum.h +++ b/libs/mavlink/include/mavlink/v1.0/checksum.h @@ -73,7 +73,7 @@ static inline uint16_t crc_calculate(const uint8_t* pBuffer, uint16_t length) * @param data new bytes to hash * @param crcAccum the already accumulated checksum **/ -static inline void crc_accumulate_buffer(uint16_t *crcAccum, const char *pBuffer, uint8_t length) +static inline void crc_accumulate_buffer(uint16_t *crcAccum, const char *pBuffer, uint16_t length) { const uint8_t *p = (const uint8_t *)pBuffer; while (length--) { diff --git a/libs/mavlink/include/mavlink/v1.0/common/common.h b/libs/mavlink/include/mavlink/v1.0/common/common.h index 020211d4063fbd3e428e7ae8d2100c06b3d2e8ce..de6e22011d827c75516cfca161aabef614edd664 100644 --- a/libs/mavlink/include/mavlink/v1.0/common/common.h +++ b/libs/mavlink/include/mavlink/v1.0/common/common.h @@ -5,6 +5,10 @@ #ifndef COMMON_H #define COMMON_H +#ifndef MAVLINK_H + #error Wrong include order: common.h MUST NOT BE DIRECTLY USED. Include mavlink.h from the same directory instead or set all defines from mavlink.h manually. +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/libs/mavlink/include/mavlink/v1.0/mavlink_helpers.h b/libs/mavlink/include/mavlink/v1.0/mavlink_helpers.h index 96672f847f43185e52305b25d36575c9880c6404..1639a830bb27541dea98dff682e8c16d7fe68fe1 100644 --- a/libs/mavlink/include/mavlink/v1.0/mavlink_helpers.h +++ b/libs/mavlink/include/mavlink/v1.0/mavlink_helpers.h @@ -73,7 +73,6 @@ MAVLINK_HELPER uint16_t mavlink_finalize_message_chan(mavlink_message_t* msg, ui #endif { // This code part is the same for all messages; - uint16_t checksum; msg->magic = MAVLINK_STX; msg->len = length; msg->sysid = system_id; @@ -81,12 +80,13 @@ MAVLINK_HELPER uint16_t mavlink_finalize_message_chan(mavlink_message_t* msg, ui // One sequence number per component msg->seq = mavlink_get_channel_status(chan)->current_tx_seq; mavlink_get_channel_status(chan)->current_tx_seq = mavlink_get_channel_status(chan)->current_tx_seq+1; - checksum = crc_calculate((uint8_t*)&msg->len, length + MAVLINK_CORE_HEADER_LEN); + msg->checksum = crc_calculate(((const uint8_t*)(msg)) + 3, MAVLINK_CORE_HEADER_LEN); + crc_accumulate_buffer(&msg->checksum, _MAV_PAYLOAD(msg), msg->len); #if MAVLINK_CRC_EXTRA - crc_accumulate(crc_extra, &checksum); + crc_accumulate(crc_extra, &msg->checksum); #endif - mavlink_ck_a(msg) = (uint8_t)(checksum & 0xFF); - mavlink_ck_b(msg) = (uint8_t)(checksum >> 8); + mavlink_ck_a(msg) = (uint8_t)(msg->checksum & 0xFF); + mavlink_ck_b(msg) = (uint8_t)(msg->checksum >> 8); return length + MAVLINK_NUM_NON_PAYLOAD_BYTES; } @@ -133,7 +133,7 @@ MAVLINK_HELPER void _mav_finalize_message_chan_send(mavlink_channel_t chan, uint buf[4] = mavlink_system.compid; buf[5] = msgid; status->current_tx_seq++; - checksum = crc_calculate((uint8_t*)&buf[1], MAVLINK_CORE_HEADER_LEN); + checksum = crc_calculate((const uint8_t*)&buf[1], MAVLINK_CORE_HEADER_LEN); crc_accumulate_buffer(&checksum, packet, length); #if MAVLINK_CRC_EXTRA crc_accumulate(crc_extra, &checksum); @@ -158,6 +158,7 @@ MAVLINK_HELPER void _mavlink_resend_uart(mavlink_channel_t chan, const mavlink_m ck[0] = (uint8_t)(msg->checksum & 0xFF); ck[1] = (uint8_t)(msg->checksum >> 8); + // XXX use the right sequence here MAVLINK_START_UART_SEND(chan, MAVLINK_NUM_NON_PAYLOAD_BYTES + msg->len); _mavlink_send_uart(chan, (const char *)&msg->magic, MAVLINK_NUM_HEADER_BYTES); @@ -172,7 +173,13 @@ MAVLINK_HELPER void _mavlink_resend_uart(mavlink_channel_t chan, const mavlink_m */ MAVLINK_HELPER uint16_t mavlink_msg_to_send_buffer(uint8_t *buffer, const mavlink_message_t *msg) { - memcpy(buffer, (const uint8_t *)&msg->magic, MAVLINK_NUM_NON_PAYLOAD_BYTES + (uint16_t)msg->len); + memcpy(buffer, (const uint8_t *)&msg->magic, MAVLINK_NUM_HEADER_BYTES + (uint16_t)msg->len); + + uint8_t *ck = buffer + (MAVLINK_NUM_HEADER_BYTES + (uint16_t)msg->len); + + ck[0] = (uint8_t)(msg->checksum & 0xFF); + ck[1] = (uint8_t)(msg->checksum >> 8); + return MAVLINK_NUM_NON_PAYLOAD_BYTES + (uint16_t)msg->len; } diff --git a/libs/mavlink/include/mavlink/v1.0/mavlink_types.h b/libs/mavlink/include/mavlink/v1.0/mavlink_types.h index 4019c619e00b019882ae18ed9bbc7497aa0a4668..43658e629e6446b91853a38df10debfe36330bfd 100644 --- a/libs/mavlink/include/mavlink/v1.0/mavlink_types.h +++ b/libs/mavlink/include/mavlink/v1.0/mavlink_types.h @@ -28,6 +28,7 @@ #define MAVLINK_MAX_EXTENDED_PAYLOAD_LEN (MAVLINK_MAX_EXTENDED_PACKET_LEN - MAVLINK_EXTENDED_HEADER_LEN - MAVLINK_NUM_NON_PAYLOAD_BYTES) +#pragma pack(push, 1) typedef struct param_union { union { float param_float; @@ -62,13 +63,12 @@ typedef struct __mavlink_message { uint64_t payload64[(MAVLINK_MAX_PAYLOAD_LEN+MAVLINK_NUM_CHECKSUM_BYTES+7)/8]; } mavlink_message_t; - typedef struct __mavlink_extended_message { mavlink_message_t base_msg; int32_t extended_payload_len; ///< Length of extended payload if any uint8_t extended_payload[MAVLINK_MAX_EXTENDED_PAYLOAD_LEN]; } mavlink_extended_message_t; - +#pragma pack(pop) typedef enum { MAVLINK_TYPE_CHAR = 0, diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 90d0a58e570bb2ae84b0d9442c97335d903b82f4..f26f8053b49efff8fe6cad056adcdd092990cb17 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -186,22 +186,27 @@ DebugBuild { src/qgcunittest/MockUASManager.h \ src/qgcunittest/MockUAS.h \ src/qgcunittest/MockQGCUASParamManager.h \ + src/qgcunittest/MockMavlinkInterface.h \ + src/qgcunittest/MockMavlinkFileServer.h \ src/qgcunittest/MultiSignalSpy.h \ src/qgcunittest/FlightModeConfigTest.h \ src/qgcunittest/FlightGearTest.h \ src/qgcunittest/TCPLinkTest.h \ - src/qgcunittest/TCPLoopBackServer.h + src/qgcunittest/TCPLoopBackServer.h \ + src/qgcunittest/QGCUASFileManagerTest.h SOURCES += \ src/qgcunittest/UASUnitTest.cc \ src/qgcunittest/MockUASManager.cc \ src/qgcunittest/MockUAS.cc \ src/qgcunittest/MockQGCUASParamManager.cc \ + src/qgcunittest/MockMavlinkFileServer.cc \ src/qgcunittest/MultiSignalSpy.cc \ src/qgcunittest/FlightModeConfigTest.cc \ src/qgcunittest/FlightGearTest.cc \ src/qgcunittest/TCPLinkTest.cc \ - src/qgcunittest/TCPLoopBackServer.cc + src/qgcunittest/TCPLoopBackServer.cc \ + src/qgcunittest/QGCUASFileManagerTest.cc } # @@ -296,6 +301,7 @@ FORMS += \ src/ui/designer/QGCCommandButton.ui \ src/ui/QGCMAVLinkLogPlayer.ui \ src/ui/QGCWaypointListMulti.ui \ + src/ui/QGCUASFileViewMulti.ui \ src/ui/QGCUDPLinkConfiguration.ui \ src/ui/QGCTCPLinkConfiguration.ui \ src/ui/QGCSettingsWidget.ui \ @@ -373,7 +379,8 @@ FORMS += \ src/ui/px4_configuration/QGCPX4AirframeConfig.ui \ src/ui/px4_configuration/QGCPX4MulticopterConfig.ui \ src/ui/px4_configuration/QGCPX4SensorCalibration.ui \ - src/ui/designer/QGCXYPlot.ui + src/ui/designer/QGCXYPlot.ui \ + src/ui/QGCUASFileView.ui HEADERS += \ src/MG.h \ @@ -461,6 +468,7 @@ HEADERS += \ src/comm/MAVLinkSimulationMAV.h \ src/uas/QGCMAVLinkUASFactory.h \ src/ui/QGCWaypointListMulti.h \ + src/ui/QGCUASFileViewMulti.h \ src/ui/QGCUDPLinkConfiguration.h \ src/ui/QGCTCPLinkConfiguration.h \ src/ui/QGCSettingsWidget.h \ @@ -567,6 +575,8 @@ HEADERS += \ src/ui/menuactionhelper.h \ src/uas/UASManagerInterface.h \ src/uas/QGCUASParamManagerInterface.h \ + src/uas/QGCUASFileManager.h \ + src/ui/QGCUASFileView.h \ src/uas/QGCUASWorker.h \ src/CmdLineOptParser.h \ src/uas/QGXPX4UAS.h @@ -651,6 +661,7 @@ SOURCES += \ src/comm/MAVLinkSimulationMAV.cc \ src/uas/QGCMAVLinkUASFactory.cc \ src/ui/QGCWaypointListMulti.cc \ + src/ui/QGCUASFileViewMulti.cc \ src/ui/QGCUDPLinkConfiguration.cc \ src/ui/QGCTCPLinkConfiguration.cc \ src/ui/QGCSettingsWidget.cc \ @@ -753,6 +764,8 @@ SOURCES += \ src/ui/px4_configuration/QGCPX4SensorCalibration.cc \ src/ui/designer/QGCXYPlot.cc \ src/ui/menuactionhelper.cpp \ + src/uas/QGCUASFileManager.cc \ + src/ui/QGCUASFileView.cc \ src/uas/QGCUASWorker.cc \ src/CmdLineOptParser.cc \ src/uas/QGXPX4UAS.cc diff --git a/src/qgcunittest/MockMavlinkFileServer.cc b/src/qgcunittest/MockMavlinkFileServer.cc new file mode 100644 index 0000000000000000000000000000000000000000..65776119fcacd2e3dc61856bfe4a88f31e835679 --- /dev/null +++ b/src/qgcunittest/MockMavlinkFileServer.cc @@ -0,0 +1,286 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include "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; ihdr.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 for writing, returns + case QGCUASFileManager::kCmdWrite: + // appends bytes at in + 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); +} \ No newline at end of file diff --git a/src/qgcunittest/MockMavlinkFileServer.h b/src/qgcunittest/MockMavlinkFileServer.h new file mode 100644 index 0000000000000000000000000000000000000000..7c581c788b26dce8b36aa3a77f75ccc4f6a3267d --- /dev/null +++ b/src/qgcunittest/MockMavlinkFileServer.h @@ -0,0 +1,78 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#ifndef 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 + +#include + +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 diff --git a/src/qgcunittest/MockMavlinkInterface.cc b/src/qgcunittest/MockMavlinkInterface.cc new file mode 100644 index 0000000000000000000000000000000000000000..d9313fa90294e5419604bc179243eb52709dc209 --- /dev/null +++ b/src/qgcunittest/MockMavlinkInterface.cc @@ -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" diff --git a/src/qgcunittest/MockMavlinkInterface.h b/src/qgcunittest/MockMavlinkInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..5aaab3d6a18809abaeb89fef3e8c0a8779118f2d --- /dev/null +++ b/src/qgcunittest/MockMavlinkInterface.h @@ -0,0 +1,44 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include + +#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 diff --git a/src/qgcunittest/MockUAS.cc b/src/qgcunittest/MockUAS.cc index ff3688e40ac100c529c023f6081e871fb4d9358e..82dedffc2f10f1a7ff87b4647184e4d50faa43f9 100644 --- a/src/qgcunittest/MockUAS.cc +++ b/src/qgcunittest/MockUAS.cc @@ -27,7 +27,8 @@ QString MockUAS::_bogusStaticString; MockUAS::MockUAS(void) : _systemType(MAV_TYPE_QUADROTOR), - _systemId(1) + _systemId(1), + _mavlinkPlugin(NULL) { } @@ -41,4 +42,13 @@ void MockUAS::setMockParametersAndSignal(MockQGCUASParamManager::ParamMap_t& map i.next(); emit parameterChanged(_systemId, 0, i.key(), i.value()); } +} + +void MockUAS::sendMessage(mavlink_message_t message) +{ + if (!_mavlinkPlugin) { + Q_ASSERT(false); + } + + _mavlinkPlugin->sendMessage(message); } \ No newline at end of file diff --git a/src/qgcunittest/MockUAS.h b/src/qgcunittest/MockUAS.h index 8f6ad10b430f5d2d7c0b8651906d919e7eeddafd..5b3a529982517314a5dc18c861d2eb544cab6a4b 100644 --- a/src/qgcunittest/MockUAS.h +++ b/src/qgcunittest/MockUAS.h @@ -26,6 +26,7 @@ #include "UASInterface.h" #include "MockQGCUASParamManager.h" +#include "MockMavlinkInterface.h" #include @@ -50,6 +51,9 @@ public: virtual int getUASID(void) const { return _systemId; } virtual QGCUASParamManagerInterface* getParamManager() { return &_paramManager; }; + // sendMessage is only supported if a mavlink plugin is installed. + virtual void sendMessage(mavlink_message_t message); + public: // MockUAS methods MockUAS(void); @@ -63,12 +67,15 @@ public: /// allows you to simulate parameter input and validate parameter setting MockQGCUASParamManager* getMockQGCUASParamManager(void) { return &_paramManager; } - /// Sets the parameter map into the mock QGCUASParamManager and signals parameterChanged for + /// @brief Sets the parameter map into the mock QGCUASParamManager and signals parameterChanged for /// each param void setMockParametersAndSignal(MockQGCUASParamManager::ParamMap_t& map); void emitRemoteControlChannelRawChanged(int channel, float raw) { emit remoteControlChannelRawChanged(channel, raw); } + /// @brief Installs a mavlink plugin. Only a single mavlink plugin is supported at a time. + void setMockMavlinkPlugin(MockMavlinkInterface* mavlinkPlugin) { _mavlinkPlugin = mavlinkPlugin; }; + public: // Unimplemented UASInterface overrides virtual QString getUASName() const { Q_ASSERT(false); return _bogusString; }; @@ -109,6 +116,10 @@ public: virtual bool systemCanReverse() const { Q_ASSERT(false); return false; }; virtual QString getSystemTypeName() { Q_ASSERT(false); return _bogusString; }; virtual int getAutopilotType() { Q_ASSERT(false); return 0; }; + virtual QGCUASFileManager* getFileManager() {Q_ASSERT(false); return NULL; } + + /** @brief Send a message over this link (to this or to all UAS on this link) */ + virtual void sendMessage(LinkInterface* link, mavlink_message_t message){ Q_UNUSED(link); Q_UNUSED(message); Q_ASSERT(false); } virtual QString getAutopilotTypeName() { Q_ASSERT(false); return _bogusString; }; virtual void setAutopilotType(int apType) { Q_UNUSED(apType); Q_ASSERT(false); }; virtual QMap getComponents() { Q_ASSERT(false); return _bogusMapIntQString; }; @@ -176,6 +187,8 @@ private: int _systemId; MockQGCUASParamManager _paramManager; + + MockMavlinkInterface* _mavlinkPlugin; ///< Mock Mavlink plugin, NULL for none // Bogus variables used for return types of NYI methods QString _bogusString; diff --git a/src/qgcunittest/QGCUASFileManagerTest.cc b/src/qgcunittest/QGCUASFileManagerTest.cc new file mode 100644 index 0000000000000000000000000000000000000000..7da1dbdab6e78af49fa93ffccaa6795dbcd7246a --- /dev/null +++ b/src/qgcunittest/QGCUASFileManagerTest.cc @@ -0,0 +1,224 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include "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 + +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; icheckNoSignals() == 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; idownloadPath(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); + } +} diff --git a/src/qgcunittest/QGCUASFileManagerTest.h b/src/qgcunittest/QGCUASFileManagerTest.h new file mode 100644 index 0000000000000000000000000000000000000000..a2c6ac32e00fe165d983bde6a89f652b4d1f9d19 --- /dev/null +++ b/src/qgcunittest/QGCUASFileManagerTest.h @@ -0,0 +1,95 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#ifndef QGCUASFILEMANAGERTEST_H +#define QGCUASFILEMANAGERTEST_H + +#include +#include + +#include "AutoTest.h" +#include "MockUAS.h" +#include "MockMavlinkFileServer.h" +#include "QGCUASFileManager.h" +#include "MultiSignalSpy.h" + +/// @file +/// @brief QGCUASFileManager unit test +/// +/// @author Don Gagne + +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 diff --git a/src/uas/QGCMAVLinkUASFactory.cc b/src/uas/QGCMAVLinkUASFactory.cc index 0906e6c4fdaeae63097425145421a596cefc8c28..ccf4c0673fa76c52d303edd0df5ec037da2eb577 100644 --- a/src/uas/QGCMAVLinkUASFactory.cc +++ b/src/uas/QGCMAVLinkUASFactory.cc @@ -35,6 +35,7 @@ UASInterface* QGCMAVLinkUASFactory::createUAS(MAVLinkProtocol* mavlink, LinkInte // Connect this robot to the UAS object connect(mavlink, SIGNAL(messageReceived(LinkInterface*, mavlink_message_t)), mav, SLOT(receiveMessage(LinkInterface*, mavlink_message_t))); + connect(mavlink, SIGNAL(messageReceived(LinkInterface*,mavlink_message_t)), mav->getFileManager(), SLOT(receiveMessage(LinkInterface*, mavlink_message_t))); #ifdef QGC_PROTOBUF_ENABLED connect(mavlink, SIGNAL(extendedMessageReceived(LinkInterface*, std::tr1::shared_ptr)), mav, SLOT(receiveExtendedMessage(LinkInterface*, std::tr1::shared_ptr))); #endif diff --git a/src/uas/QGCUASFileManager.cc b/src/uas/QGCUASFileManager.cc new file mode 100644 index 0000000000000000000000000000000000000000..41b726d993f7f52873cf71bee87b5490470143b4 --- /dev/null +++ b/src/uas/QGCUASFileManager.cc @@ -0,0 +1,519 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include "QGCUASFileManager.h" +#include "QGC.h" +#include "MAVLinkProtocol.h" + +#include +#include +#include + +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); +} diff --git a/src/uas/QGCUASFileManager.h b/src/uas/QGCUASFileManager.h new file mode 100644 index 0000000000000000000000000000000000000000..5a3bc8432df5c7502a4f9f49280f7d5ee2830129 --- /dev/null +++ b/src/uas/QGCUASFileManager.h @@ -0,0 +1,164 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#ifndef QGCUASFILEMANAGER_H +#define QGCUASFILEMANAGER_H + +#include +#include + +#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 from + kCmdOpen, ///> opens for reading, returns + kCmdRead, ///> reads bytes from in + kCmdCreate, ///> creates for writing, returns + kCmdWrite, ///> appends bytes at in + 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 diff --git a/src/uas/UAS.cc b/src/uas/UAS.cc index 652ad6a3cc897e2486f3ca8215f41d3fb4dc4cf3..f26acea0be38f773943d68a82747a6667f48001b 100644 --- a/src/uas/UAS.cc +++ b/src/uas/UAS.cc @@ -139,6 +139,7 @@ UAS::UAS(MAVLinkProtocol* protocol, QThread* thread, int id) : UASInterface(), airSpeed(std::numeric_limits::quiet_NaN()), groundSpeed(std::numeric_limits::quiet_NaN()), waypointManager(this), + fileManager(this, this), attitudeKnown(false), attitudeStamped(false), @@ -148,6 +149,8 @@ UAS::UAS(MAVLinkProtocol* protocol, QThread* thread, int id) : UASInterface(), pitch(0.0), yaw(0.0), + imagePackets(0), // We must initialize to 0, otherwise extended data packets maybe incorrectly thought to be images + blockHomePositionChanges(false), receivedMode(false), @@ -175,6 +178,8 @@ UAS::UAS(MAVLinkProtocol* protocol, QThread* thread, int id) : UASInterface(), componentMulti[i] = false; } + connect(mavlink, SIGNAL(messageReceived(LinkInterface*,mavlink_message_t)), &fileManager, SLOT(receiveMessage(LinkInterface*,mavlink_message_t))); + // Store a list of available actions for this UAS. // Basically everything exposed as a SLOT with no return value or arguments. @@ -1368,6 +1373,7 @@ void UAS::receiveMessage(LinkInterface* link, mavlink_message_t message) // NO VALID TRANSACTION - ABORT // Restart statemachine imagePacketsArrived = 0; + break; } for (int i = 0; i < imagePayload; ++i) @@ -1384,6 +1390,8 @@ void UAS::receiveMessage(LinkInterface* link, mavlink_message_t message) if ((imagePacketsArrived >= imagePackets)) { // Restart statemachine + imagePackets = 0; + imagePacketsArrived = 0; emit imageReady(this); //qDebug() << "imageReady emitted. all packets arrived"; } diff --git a/src/uas/UAS.h b/src/uas/UAS.h index ca048d4c2b71c5a21c1e339151894dfae9ca2115..0c623e3a79aeae2c3d9f376eac978d562e5b7036 100644 --- a/src/uas/UAS.h +++ b/src/uas/UAS.h @@ -42,6 +42,7 @@ This file is part of the QGROUNDCONTROL project #include "QGCJSBSimLink.h" #include "QGCXPlaneLink.h" #include "QGCUASParamManager.h" +#include "QGCUASFileManager.h" /** @@ -370,6 +371,7 @@ public: #endif friend class UASWaypointManager; + friend class QGCUASFileManager; protected: //COMMENTS FOR TEST UNIT /// LINK ID AND STATUS @@ -472,6 +474,7 @@ protected: //COMMENTS FOR TEST UNIT double groundSpeed; ///< Groundspeed double bearingToWaypoint; ///< Bearing to next waypoint UASWaypointManager waypointManager; + QGCUASFileManager fileManager; /// ATTITUDE bool attitudeKnown; ///< True if attitude was received, false else @@ -554,6 +557,10 @@ public: return ¶mMgr; } + virtual QGCUASFileManager* getFileManager() { + return &fileManager; + } + /** @brief Get the HIL simulation */ QGCHilLink* getHILSimulation() const { return simulation; diff --git a/src/uas/UASInterface.h b/src/uas/UASInterface.h index 7a910376638db93bd954f2ac7bbf4cacf9983817..cf697ea9b46826bc93cc9c2b7e114018a1a8e293 100644 --- a/src/uas/UASInterface.h +++ b/src/uas/UASInterface.h @@ -52,6 +52,8 @@ This file is part of the QGROUNDCONTROL project #endif #endif +class QGCUASFileManager; + enum BatteryType { NICD = 0, @@ -158,6 +160,13 @@ public: /** @brief Get reference to the param manager **/ virtual QGCUASParamManagerInterface* getParamManager() = 0; + virtual QGCUASFileManager* getFileManager() = 0; + + /** @brief Send a message over this link (to this or to all UAS on this link) */ + virtual void sendMessage(LinkInterface* link, mavlink_message_t message) = 0; + /** @brief Send a message over all links this UAS can be reached with (!= all links) */ + virtual void sendMessage(mavlink_message_t message) = 0; + /* COMMUNICATION FLAGS */ enum CommStatus { diff --git a/src/ui/MainWindow.cc b/src/ui/MainWindow.cc index d38db5f4baea97079c6f71bdfc9594e6fa204b57..5d89ce13c94d26d467cf0179a0570d8b8a10441f 100644 --- a/src/ui/MainWindow.cc +++ b/src/ui/MainWindow.cc @@ -71,6 +71,7 @@ This file is part of the QGROUNDCONTROL project #include "SerialSettingsDialog.h" #include "terminalconsole.h" #include "menuactionhelper.h" +#include "QGCUASFileViewMulti.h" #include // Add support for the MAVLink generator UI if it's been requested. @@ -657,6 +658,7 @@ void MainWindow::buildCommonWidgets() createDockWidget(engineeringView,new QGCMAVLinkInspector(mavlink,this),tr("MAVLink Inspector"),"MAVLINK_INSPECTOR_DOCKWIDGET",VIEW_ENGINEER,Qt::RightDockWidgetArea); createDockWidget(engineeringView,new ParameterInterface(this),tr("Onboard Parameters"),"PARAMETER_INTERFACE_DOCKWIDGET",VIEW_ENGINEER,Qt::RightDockWidgetArea); + createDockWidget(engineeringView,new QGCUASFileViewMulti(this),tr("Onboard Files"),"FILE_VIEW_DOCKWIDGET",VIEW_ENGINEER,Qt::RightDockWidgetArea); createDockWidget(simView,new ParameterInterface(this),tr("Onboard Parameters"),"PARAMETER_INTERFACE_DOCKWIDGET",VIEW_SIMULATION,Qt::RightDockWidgetArea); menuActionHelper->createToolAction(tr("Map View"), "MAP_VIEW_DOCKWIDGET"); @@ -818,6 +820,10 @@ void MainWindow::loadDockWidget(const QString& name) { createDockWidget(centerStack->currentWidget(),new ParameterInterface(this),tr("Onboard Parameters"),"PARAMETER_INTERFACE_DOCKWIDGET",currentView,Qt::RightDockWidgetArea); } + else if (name == "FILE_VIEW_DOCKWIDGET") + { + createDockWidget(centerStack->currentWidget(),new QGCUASFileViewMulti(this),tr("Onboard Files"),"FILE_VIEW_DOCKWIDGET",VIEW_ENGINEER,Qt::RightDockWidgetArea); + } else if (name == "UAS_STATUS_DETAILS_DOCKWIDGET") { createDockWidget(centerStack->currentWidget(),new UASInfoWidget(this),tr("Status Details"),"UAS_STATUS_DETAILS_DOCKWIDGET",currentView,Qt::RightDockWidgetArea); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 2a08c516a33b647ee6726c535ba6fa26f38a3fad..5cc2545fa30599b5d1a53c0e537e713f7d0e4cda 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -76,6 +76,7 @@ This file is part of the QGROUNDCONTROL project #include "QGCMAVLinkLogPlayer.h" #include "QGCVehicleConfig.h" #include "MAVLinkDecoder.h" +#include "QGCUASFileViewMulti.h" class QGCMapTool; class QGCMAVLinkMessageSender; @@ -86,6 +87,7 @@ class Linecharts; class QGCDataPlot2D; class JoystickWidget; class MenuActionHelper; +class QGCUASFileViewMulti; /** * @brief Main Application Window @@ -476,6 +478,8 @@ protected: QGCMAVLinkLogPlayer* logPlayer; QMap hilDocks; + QPointer fileWidget; + // Popup widgets JoystickWidget* joystickWidget; diff --git a/src/ui/QGCHilXPlaneConfiguration.cc b/src/ui/QGCHilXPlaneConfiguration.cc index dc1cd52b6ac278818cec038382119fe5272e3171..6ba50eda41f33ebfe44154a1c7c73322a73c56a6 100644 --- a/src/ui/QGCHilXPlaneConfiguration.cc +++ b/src/ui/QGCHilXPlaneConfiguration.cc @@ -50,6 +50,10 @@ void QGCHilXPlaneConfiguration::setVersion(int version) void QGCHilXPlaneConfiguration::toggleSimulation(bool connect) { + if (!link) { + return; + } + Q_UNUSED(connect); if (!link->isConnected()) { diff --git a/src/ui/QGCUASFileView.cc b/src/ui/QGCUASFileView.cc new file mode 100644 index 0000000000000000000000000000000000000000..a9ac957363f162fdd1102bb19922451160b3bfcc --- /dev/null +++ b/src/ui/QGCUASFileView.cc @@ -0,0 +1,199 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#include "QGCUASFileView.h" +#include "uas/QGCUASFileManager.h" + +#include +#include +#include + +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); +} diff --git a/src/ui/QGCUASFileView.h b/src/ui/QGCUASFileView.h new file mode 100644 index 0000000000000000000000000000000000000000..0b33e5b3b35e7b1f9b15044546dcecbd8a9a382e --- /dev/null +++ b/src/ui/QGCUASFileView.h @@ -0,0 +1,62 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +#ifndef QGCUASFILEVIEW_H +#define QGCUASFILEVIEW_H + +#include +#include + +#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 _walkIndexStack; + QList _walkItemStack; + Ui::QGCUASFileView _ui; +}; + +#endif // QGCUASFILEVIEW_H diff --git a/src/ui/QGCUASFileView.ui b/src/ui/QGCUASFileView.ui new file mode 100644 index 0000000000000000000000000000000000000000..1bb3552c465f324170c4de13be7b950ab4faa666 --- /dev/null +++ b/src/ui/QGCUASFileView.ui @@ -0,0 +1,50 @@ + + + QGCUASFileView + + + + 0 + 0 + 414 + 518 + + + + Form + + + + + + List Files + + + + + + + true + + + + 1 + + + + + + + + false + + + Download File + + + + + + + + diff --git a/src/ui/QGCUASFileViewMulti.cc b/src/ui/QGCUASFileViewMulti.cc new file mode 100644 index 0000000000000000000000000000000000000000..fb627ea044a2af5163ff34746f906ca73863fc9a --- /dev/null +++ b/src/ui/QGCUASFileViewMulti.cc @@ -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(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; + } +} diff --git a/src/ui/QGCUASFileViewMulti.h b/src/ui/QGCUASFileViewMulti.h new file mode 100644 index 0000000000000000000000000000000000000000..6b77613ecbeb227fe73801393558b1712fe6f32f --- /dev/null +++ b/src/ui/QGCUASFileViewMulti.h @@ -0,0 +1,36 @@ +#ifndef QGCUASFILEVIEWMULTI_H +#define QGCUASFILEVIEWMULTI_H + +#include +#include + +#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 lists; + +private: + Ui::QGCUASFileViewMulti *ui; +}; + +#endif // QGCUASFILEVIEWMULTI_H diff --git a/src/ui/QGCUASFileViewMulti.ui b/src/ui/QGCUASFileViewMulti.ui new file mode 100644 index 0000000000000000000000000000000000000000..169c00a1f0deca2ace6100bafbb792e8e8a24924 --- /dev/null +++ b/src/ui/QGCUASFileViewMulti.ui @@ -0,0 +1,27 @@ + + + QGCUASFileViewMulti + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + + + + + + +