From 381acec96b42584122cea261324e1f71c99a6b98 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 12 Feb 2015 16:09:24 -0800 Subject: [PATCH] Convert Firmware Upgrade to QML - Much simpler UI - MVC style qml programming model --- qgroundcontrol.pro | 17 +- src/VehicleSetup/FirmwareUpgrade.qml | 81 +- src/VehicleSetup/FirmwareUpgradeController.cc | 612 +++++++++++++++ src/VehicleSetup/FirmwareUpgradeController.h | 148 ++++ src/VehicleSetup/PX4Bootloader.cc | 466 ++++++++++++ src/VehicleSetup/PX4Bootloader.h | 165 +++++ src/VehicleSetup/PX4FirmwareUpgradeThread.cc | 296 ++++++++ src/VehicleSetup/PX4FirmwareUpgradeThread.h | 183 +++++ src/VehicleSetup/SetupView.cc | 13 +- src/ui/px4_configuration/PX4Bootloader.cc | 466 ------------ src/ui/px4_configuration/PX4Bootloader.h | 165 ----- src/ui/px4_configuration/PX4FirmwareUpgrade.cc | 824 --------------------- src/ui/px4_configuration/PX4FirmwareUpgrade.h | 160 ---- src/ui/px4_configuration/PX4FirmwareUpgrade.ui | 263 ------- .../px4_configuration/PX4FirmwareUpgradeThread.cc | 292 -------- .../px4_configuration/PX4FirmwareUpgradeThread.h | 183 ----- 16 files changed, 1958 insertions(+), 2376 deletions(-) create mode 100644 src/VehicleSetup/FirmwareUpgradeController.cc create mode 100644 src/VehicleSetup/FirmwareUpgradeController.h create mode 100644 src/VehicleSetup/PX4Bootloader.cc create mode 100644 src/VehicleSetup/PX4Bootloader.h create mode 100644 src/VehicleSetup/PX4FirmwareUpgradeThread.cc create mode 100644 src/VehicleSetup/PX4FirmwareUpgradeThread.h delete mode 100644 src/ui/px4_configuration/PX4Bootloader.cc delete mode 100644 src/ui/px4_configuration/PX4Bootloader.h delete mode 100644 src/ui/px4_configuration/PX4FirmwareUpgrade.cc delete mode 100644 src/ui/px4_configuration/PX4FirmwareUpgrade.h delete mode 100644 src/ui/px4_configuration/PX4FirmwareUpgrade.ui delete mode 100644 src/ui/px4_configuration/PX4FirmwareUpgradeThread.cc delete mode 100644 src/ui/px4_configuration/PX4FirmwareUpgradeThread.h diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index e80135e..5f6862c 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -340,7 +340,6 @@ FORMS += \ src/ui/px4_configuration/QGCPX4MulticopterConfig.ui \ src/ui/px4_configuration/QGCPX4SensorCalibration.ui \ src/ui/px4_configuration/PX4RCCalibration.ui \ - src/ui/px4_configuration/PX4FirmwareUpgrade.ui \ src/ui/QGCUASFileView.ui \ src/QGCQmlWidgetHolder.ui \ src/ui/QGCMapRCToParamDialog.ui \ @@ -477,9 +476,6 @@ HEADERS += \ src/ui/px4_configuration/QGCPX4SensorCalibration.h \ src/ui/px4_configuration/PX4RCCalibration.h \ src/ui/px4_configuration/RCValueWidget.h \ - src/ui/px4_configuration/PX4Bootloader.h \ - src/ui/px4_configuration/PX4FirmwareUpgradeThread.h \ - src/ui/px4_configuration/PX4FirmwareUpgrade.h \ src/uas/UASManagerInterface.h \ src/uas/QGCUASParamManagerInterface.h \ src/uas/QGCUASFileManager.h \ @@ -622,9 +618,6 @@ SOURCES += \ src/ui/px4_configuration/QGCPX4SensorCalibration.cc \ src/ui/px4_configuration/PX4RCCalibration.cc \ src/ui/px4_configuration/RCValueWidget.cc \ - src/ui/px4_configuration/PX4Bootloader.cc \ - src/ui/px4_configuration/PX4FirmwareUpgradeThread.cc \ - src/ui/px4_configuration/PX4FirmwareUpgrade.cc \ src/uas/QGCUASFileManager.cc \ src/ui/QGCUASFileView.cc \ src/CmdLineOptParser.cc \ @@ -714,6 +707,10 @@ SOURCES += \ # # AutoPilot Plugin Support # + +INCLUDEPATH += \ + src/VehicleSetup + FORMS += \ src/VehicleSetup/ParameterEditor.ui \ src/ui/QGCPX4VehicleConfig.ui \ @@ -724,6 +721,9 @@ HEADERS+= \ src/VehicleSetup/SetupView.h \ src/VehicleSetup/ParameterEditor.h \ src/VehicleSetup/VehicleComponent.h \ + src/VehicleSetup/FirmwareUpgradeController.h \ + src/VehicleSetup/PX4Bootloader.h \ + src/VehicleSetup/PX4FirmwareUpgradeThread.h \ src/AutoPilotPlugins/AutoPilotPluginManager.h \ src/AutoPilotPlugins/AutoPilotPlugin.h \ src/AutoPilotPlugins/Generic/GenericAutoPilotPlugin.h \ @@ -742,6 +742,9 @@ SOURCES += \ src/VehicleSetup/SetupView.cc \ src/VehicleSetup/ParameterEditor.cc \ src/VehicleSetup/VehicleComponent.cc \ + src/VehicleSetup/FirmwareUpgradeController.cc \ + src/VehicleSetup/PX4Bootloader.cc \ + src/VehicleSetup/PX4FirmwareUpgradeThread.cc \ src/AutoPilotPlugins/AutoPilotPluginManager.cc \ src/AutoPilotPlugins/Generic/GenericAutoPilotPlugin.cc \ src/AutoPilotPlugins/Generic/GenericParameterFacts.cc \ diff --git a/src/VehicleSetup/FirmwareUpgrade.qml b/src/VehicleSetup/FirmwareUpgrade.qml index 1dcbdce..53f150c 100644 --- a/src/VehicleSetup/FirmwareUpgrade.qml +++ b/src/VehicleSetup/FirmwareUpgrade.qml @@ -5,33 +5,100 @@ import QtQuick.Controls.Styles 1.2 import QGroundControl.Controls 1.0 import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 +import QGroundControl.FirmwareUpgradeController 1.0 Rectangle { width: 600 - height: 400 + height: 600 property var qgcPal: QGCPalette { colorGroup: QGCPalette.Active } + property FirmwareUpgradeController controller: FirmwareUpgradeController { + upgradeButton: upgradeButton + statusLog: statusTextArea + firmwareType: FirmwareUpgradeController.StableFirmware + } color: qgcPal.window - Text { - text: "FIRMWARE UPDATE" - color: qgcPal.windowText - font.pointSize: 20 - } - Column { + anchors.fill:parent + + Text { + text: "FIRMWARE UPDATE" + color: qgcPal.windowText + font.pointSize: 20 + } + + Item { + // Just used as a spacer + height: 20 + width: 10 + } + + ExclusiveGroup { id: firmwareGroup } + QGCRadioButton { + id: stableFirwareRadio + exclusiveGroup: firmwareGroup text: qsTr("Standard Version (stable)") + checked: true + enabled: upgradeButton.enabled + onClicked: { + if (checked) + controller.firmwareType = FirmwareUpgradeController.StableFirmware + } } QGCRadioButton { + id: betaFirwareRadio + exclusiveGroup: firmwareGroup text: qsTr("Beta Testing (beta)") + enabled: upgradeButton.enabled + onClicked: { if (checked) controller.firmwareType = FirmwareUpgradeController.BetaFirmware } } QGCRadioButton { + id: devloperFirwareRadio + exclusiveGroup: firmwareGroup text: qsTr("Developer Build (master)") + enabled: upgradeButton.enabled + onClicked: { if (checked) controller.firmwareType = FirmwareUpgradeController.DeveloperFirmware } } QGCRadioButton { + id: customFirwareRadio + exclusiveGroup: firmwareGroup text: qsTr("Custom firmware file...") + enabled: upgradeButton.enabled + onClicked: { if (checked) controller.firmwareType = FirmwareUpgradeController.CustomFirmware } + } + + Item { + // Just used as a spacer + height: 20 + width: 10 + } + + QGCButton { + id: upgradeButton + text: "UPGRADE" + onClicked: { + controller.doFirmwareUpgrade(); + } + } + + Item { + // Just used as a spacer + height: 20 + width: 10 + } + + TextArea { + id: statusTextArea + width: parent.width + height: 300 + readOnly: true + style: TextAreaStyle { + textColor: qgcPal.windowText + backgroundColor: qgcPal.window + } } } } diff --git a/src/VehicleSetup/FirmwareUpgradeController.cc b/src/VehicleSetup/FirmwareUpgradeController.cc new file mode 100644 index 0000000..3435497 --- /dev/null +++ b/src/VehicleSetup/FirmwareUpgradeController.cc @@ -0,0 +1,612 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009, 2015 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +/// @file +/// @brief PX4 Firmware Upgrade UI +/// @author Don Gagne + +#include "FirmwareUpgradeController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QGCFileDialog.h" +#include "QGCMessageBox.h" + +/// @Brief Constructs a new FirmwareUpgradeController Widget. This widget is used within the PX4VehicleConfig set of screens. +FirmwareUpgradeController::FirmwareUpgradeController(void) : + _downloadManager(NULL), + _downloadNetworkReply(NULL), + _firmwareType(StableFirmware), + _upgradeButton(NULL), + _statusLog(NULL) +{ + _threadController = new PX4FirmwareUpgradeThreadController(this); + Q_CHECK_PTR(_threadController); + + /* + // FIXME: NYI + // Connect standard ui elements + connect(_ui->tryAgain, &QPushButton::clicked, this, &FirmwareUpgradeController::_tryAgainButton); + connect(_ui->cancel, &QPushButton::clicked, this, &FirmwareUpgradeController::_cancelButton); + connect(_ui->next, &QPushButton::clicked, this, &FirmwareUpgradeController::_nextButton); + connect(_ui->firmwareCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(_firmwareSelected(int))); + */ + + connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &FirmwareUpgradeController::_foundBoard); + connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBootloader, this, &FirmwareUpgradeController::_foundBootloader); + connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed, this, &FirmwareUpgradeController::_bootloaderSyncFailed); + connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error); + connect(_threadController, &PX4FirmwareUpgradeThreadController::complete, this, &FirmwareUpgradeController::_complete); + connect(_threadController, &PX4FirmwareUpgradeThreadController::findTimeout, this, &FirmwareUpgradeController::_findTimeout); + connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress); + + connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick); +} + +/// @brief Cancels the current state and returns to the begin start +void FirmwareUpgradeController::_cancel(void) +{ + // Bootloader may still still open, reboot to close and heopfully get back to FMU + _threadController->sendBootloaderReboot(); + + Q_ASSERT(_upgradeButton); + _upgradeButton->setEnabled(true); +} + +/// @brief Begins the process or searching for the board +void FirmwareUpgradeController::_findBoard(void) +{ + _appendStatusLog(tr("Plug your board into USB now...")); + _searchingForBoard = true; + _threadController->findBoard(_findBoardTimeoutMsec); +} + +/// @brief Called when board has been found by the findBoard process +void FirmwareUpgradeController::_foundBoard(bool firstTry, const QString portName, QString portDescription) +{ + if (firstTry) { + // Board is still plugged + _appendStatusLog(tr("You must unplug your board before beginning the Firmware Upgrade process.")); + _cancel(); + } else { + _portName = portName; + _portDescription = portDescription; + + _appendStatusLog(tr("Board found:")); + _appendStatusLog(tr(" Port: %1").arg(_portName)); + _appendStatusLog(tr(" Description: %1").arg(_portName)); + + _findBootloader(); + } +} + +/// @brief Begins the findBootloader process to connect to the bootloader +void FirmwareUpgradeController::_findBootloader(void) +{ + _appendStatusLog(tr("Attemping to communicate with bootloader...")); + _searchingForBoard = false; + _threadController->findBootloader(_portName, _findBootloaderTimeoutMsec); +} + +/// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine +/// to the next step. +void FirmwareUpgradeController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize) +{ + _bootloaderVersion = bootloaderVersion; + _boardID = boardID; + _boardFlashSize = flashSize; + + _appendStatusLog(tr("Connected to bootloader:")); + _appendStatusLog(tr(" Version: %1").arg(_bootloaderVersion)); + _appendStatusLog(tr(" Board ID: %1").arg(_boardID)); + _appendStatusLog(tr(" Flash size: %1").arg(_boardFlashSize)); + + _getFirmwareFile(); +} + +/// @brief Called when the findBootloader process is unable to sync to the bootloader. Moves the state +/// machine to the appropriate error state. +void FirmwareUpgradeController::_bootloaderSyncFailed(void) +{ + _appendStatusLog(tr("Unable to sync with bootloader.")); + _cancel(); +} + +/// @brief Called when the findBoard or findBootloader process times out. Moves the state machine to the +/// appropriate error state. +void FirmwareUpgradeController::_findTimeout(void) +{ + QString msg; + + if (_searchingForBoard) { + msg = tr("Unable to detect your board. If the board is currently connected via USB. Disconnect it and try Upgrade again."); + } else { + msg = tr("Unable to communicate with Bootloader. If the board is currently connected via USB. Disconnect it and try Upgrade again."); + } + _appendStatusLog(msg); + _cancel(); +} + +/// @brief Sets the board image into the icon label according to the board id. +void FirmwareUpgradeController::_setBoardIcon(int boardID) +{ + QString imageFile; + + switch (boardID) { + case _boardIDPX4FMUV1: + imageFile = ":/files/images/px4/boards/px4fmu_1.x.png"; + break; + + case _boardIDPX4Flow: + imageFile = ":/files/images/px4/boards/px4flow_1.x.png"; + break; + + case _boardIDPX4FMUV2: + imageFile = ":/files/images/px4/boards/px4fmu_2.x.png"; + break; + } + + if (!imageFile.isEmpty()) { + bool success = _boardIcon.load(imageFile); + Q_ASSERT(success); + Q_UNUSED(success); + /* + // FIXME: NYI + + int w = _ui->icon->width(); + int h = _ui->icon->height(); + + _ui->icon->setPixmap(_boardIcon.scaled(w, h, Qt::KeepAspectRatio)); + */ + } +} + +/// @brief Prompts the user to select a firmware file if needed and moves the state machine to the next state. +void FirmwareUpgradeController::_getFirmwareFile(void) +{ + static const char* rgPX4FMUV1Firmware[3] = + { + "http://px4.oznet.ch/stable/px4fmu-v1_default.px4", + "http://px4.oznet.ch/beta/px4fmu-v1_default.px4", + "http://px4.oznet.ch/continuous/px4fmu-v1_default.px4" + }; + + static const char* rgPX4FMUV2Firmware[3] = + { + "http://px4.oznet.ch/stable/px4fmu-v2_default.px4", + "http://px4.oznet.ch/beta/px4fmu-v2_default.px4", + "http://px4.oznet.ch/continuous/px4fmu-v2_default.px4" + }; + + static const char* rgPX4FlowFirmware[3] = + { + "http://px4.oznet.ch/stable/px4flow.px4", + "http://px4.oznet.ch/beta/px4flow.px4", + "http://px4.oznet.ch/continuous/px4flow.px4" + }; + + Q_ASSERT(sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FMUV2Firmware) && sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FlowFirmware)); + + const char** prgFirmware; + switch (_boardID) { + case _boardIDPX4FMUV1: + prgFirmware = rgPX4FMUV1Firmware; + break; + + case _boardIDPX4Flow: + prgFirmware = rgPX4FlowFirmware; + break; + + case _boardIDPX4FMUV2: + prgFirmware = rgPX4FMUV2Firmware; + break; + + default: + prgFirmware = NULL; + break; + } + + if (prgFirmware == NULL && _firmwareType != CustomFirmware) { + QGCMessageBox::critical(tr("Firmware Upgrade"), tr("Attemping to flash an unknown board type, you must select 'Custom firmware file'")); + _cancel(); + return; + } + + if (_firmwareType == CustomFirmware) { + _firmwareFilename = QGCFileDialog::getOpenFileName(NULL, // Parent to main window + tr("Select Firmware File"), // Dialog Caption + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), // Initial directory + tr("Firmware Files (*.px4 *.bin)")); // File filter + } else { + _firmwareFilename = prgFirmware[_firmwareType]; + } + + if (_firmwareFilename.isEmpty()) { + _cancel(); + } else { + _downloadFirmware(); + } +} + +/// @brief Begins the process of downloading the selected firmware file. +void FirmwareUpgradeController::_downloadFirmware(void) +{ + Q_ASSERT(!_firmwareFilename.isEmpty()); + + _appendStatusLog(tr("Downloading firmware...")); + _appendStatusLog(tr(" From: %1").arg(_firmwareFilename)); + + // Split out filename from path + QString firmwareFilename = QFileInfo(_firmwareFilename).fileName(); + Q_ASSERT(!firmwareFilename.isEmpty()); + + // Determine location to download file to + QString downloadFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + if (downloadFile.isEmpty()) { + downloadFile = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + if (downloadFile.isEmpty()) { + _appendStatusLog(tr("Unabled to find writable download location. Tried downloads and temp directory.")); + _cancel(); + return; + } + } + Q_ASSERT(!downloadFile.isEmpty()); + downloadFile += "/" + firmwareFilename; + + QUrl firmwareUrl; + if (_firmwareFilename.startsWith("http:")) { + firmwareUrl.setUrl(_firmwareFilename); + } else { + firmwareUrl = QUrl::fromLocalFile(_firmwareFilename); + } + Q_ASSERT(firmwareUrl.isValid()); + + QNetworkRequest networkRequest(firmwareUrl); + + // Store download file location in user attribute so we can retrieve when the download finishes + networkRequest.setAttribute(QNetworkRequest::User, downloadFile); + + _downloadManager = new QNetworkAccessManager(this); + Q_CHECK_PTR(_downloadManager); + _downloadNetworkReply = _downloadManager->get(networkRequest); + Q_ASSERT(_downloadNetworkReply); + connect(_downloadNetworkReply, &QNetworkReply::downloadProgress, this, &FirmwareUpgradeController::_downloadProgress); + connect(_downloadNetworkReply, &QNetworkReply::finished, this, &FirmwareUpgradeController::_downloadFinished); + // FIXME + //connect(_downloadNetworkReply, &QNetworkReply::error, this, &FirmwareUpgradeController::_downloadError); + connect(_downloadNetworkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(_downloadError(QNetworkReply::NetworkError))); +} + +/// @brief Updates the progress indicator while downloading +void FirmwareUpgradeController::_downloadProgress(qint64 curr, qint64 total) +{ + // Take care of cases where 0 / 0 is emitted as error return value + if (total > 0) { + // FIXME: NYI + Q_UNUSED(curr); + //_ui->progressBar->setValue((curr*100) / total); + } +} + +/// @brief Called when the firmware download completes. +void FirmwareUpgradeController::_downloadFinished(void) +{ + _appendStatusLog(tr("Download complete")); + + QNetworkReply* reply = qobject_cast(QObject::sender()); + Q_ASSERT(reply); + + Q_ASSERT(_downloadNetworkReply == reply); + + _downloadManager->deleteLater(); + _downloadManager = NULL; + + // When an error occurs or the user cancels the download, we still end up here. So bail out in + // those cases. + if (reply->error() != QNetworkReply::NoError) { + return; + } + + // Download file location is in user attribute + QString downloadFilename = reply->request().attribute(QNetworkRequest::User).toString(); + Q_ASSERT(!downloadFilename.isEmpty()); + + // Store downloaded file in download location + QFile file(downloadFilename); + if (!file.open(QIODevice::WriteOnly)) { + _appendStatusLog(tr("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString())); + _cancel(); + return; + } + + file.write(reply->readAll()); + file.close(); + + + if (downloadFilename.endsWith(".px4")) { + // We need to collect information from the .px4 file as well as pull the binary image out to a seperate file. + + QFile px4File(downloadFilename); + if (!px4File.open(QIODevice::ReadOnly | QIODevice::Text)) { + _appendStatusLog(tr("Unable to open firmware file %1, error: %2").arg(downloadFilename).arg(px4File.errorString())); + return; + } + + QByteArray bytes = px4File.readAll(); + px4File.close(); + QJsonDocument doc = QJsonDocument::fromJson(bytes); + + if (doc.isNull()) { + _appendStatusLog(tr("Supplied file is not a valid JSON document")); + _cancel(); + return; + } + + QJsonObject px4Json = doc.object(); + + // Make sure the keys we need are available + static const char* rgJsonKeys[] = { "board_id", "image_size", "description", "git_identity" }; + for (size_t i=0; i> 24) & 0xFF)); + raw.append((unsigned char)((_imageSize >> 16) & 0xFF)); + raw.append((unsigned char)((_imageSize >> 8) & 0xFF)); + raw.append((unsigned char)((_imageSize >> 0) & 0xFF)); + + QByteArray raw64 = list.first().toUtf8(); + + raw.append(QByteArray::fromBase64(raw64)); + QByteArray uncompressed = qUncompress(raw); + + QByteArray b = uncompressed; + + if (b.count() == 0) { + _appendStatusLog(tr("Firmware file has 0 length image")); + _cancel(); + return; + } + if (b.count() != (int)_imageSize) { + _appendStatusLog(tr("Image size for decompressed image does not match stored image size: Expected(%1) Actual(%2)").arg(_imageSize).arg(b.count())); + _cancel(); + return; + } + + // Pad image to 4-byte boundary + while ((b.count() % 4) != 0) { + b.append(static_cast(static_cast(0xFF))); + } + + // Store decompressed image file in same location as original download file + QDir downloadDir = QFileInfo(downloadFilename).dir(); + QString decompressFilename = downloadDir.filePath("PX4FlashUpgrade.bin"); + + QFile decompressFile(decompressFilename); + if (!decompressFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + _appendStatusLog(tr("Unable to open decompressed file %1 for writing, error: %2").arg(decompressFilename).arg(decompressFile.errorString())); + _cancel(); + return; + } + + qint64 bytesWritten = decompressFile.write(b); + if (bytesWritten != b.count()) { + _appendStatusLog(tr("Write failed for decompressed image file, error: %1").arg(decompressFile.errorString())); + _cancel(); + return; + } + decompressFile.close(); + + _firmwareFilename = decompressFilename; + } else if (downloadFilename.endsWith(".bin")) { + uint32_t firmwareBoardID = 0; + + // Take some educated guesses on board id based on firmware build system file name conventions + + if (downloadFilename.toLower().contains("px4fmu-v1")) { + firmwareBoardID = _boardIDPX4FMUV2; + } else if (downloadFilename.toLower().contains("px4flow")) { + firmwareBoardID = _boardIDPX4Flow; + } else if (downloadFilename.toLower().contains("px4fmu-v1")) { + firmwareBoardID = _boardIDPX4FMUV1; + } + + if (firmwareBoardID != 0 && firmwareBoardID != _boardID) { + _appendStatusLog(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardID).arg(_boardID)); + _cancel(); + return; + } + + _firmwareFilename = downloadFilename; + + QFile binFile(_firmwareFilename); + if (!binFile.open(QIODevice::ReadOnly)) { + _appendStatusLog(tr("Unabled to open firmware file %1, %2").arg(_firmwareFilename).arg(binFile.errorString())); + _cancel(); + return; + } + _imageSize = (uint32_t)binFile.size(); + binFile.close(); + } else { + // Standard firmware builds (stable/continuous/...) are always .bin or .px4. Select file dialog for custom + // firmware filters to .bin and .px4. So we should never get a file that ends in anything else. + Q_ASSERT(false); + } + + if (_imageSize > _boardFlashSize) { + _appendStatusLog(tr("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_boardFlashSize)); + _cancel(); + return; + } + + _erase(); +} + +/// @brief Called when an error occurs during download +void FirmwareUpgradeController::_downloadError(QNetworkReply::NetworkError code) +{ + if (code == QNetworkReply::OperationCanceledError) { + _appendStatusLog(tr("Download cancelled")); + } else { + _appendStatusLog(tr("Error during download. Error: %1").arg(code)); + } + _cancel(); +} + +/// @brief Erase the board +void FirmwareUpgradeController::_erase(void) +{ + _appendStatusLog(tr("Erasing previous firmware...")); + + // We set up our own progress bar for erase since the erase command does not provide one + _eraseTickCount = 0; + _eraseTimer.start(_eraseTickMsec); + + // Erase command + _threadController->erase(); +} + +/// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the +/// appropriate next step. +void FirmwareUpgradeController::_complete(const int command) +{ + if (command == PX4FirmwareUpgradeThreadWorker::commandProgram) { + _appendStatusLog(tr("Verifying board programming...")); + _threadController->verify(_firmwareFilename); + } else if (command == PX4FirmwareUpgradeThreadWorker::commandVerify) { + _appendStatusLog(tr("Upgrade complete")); + QGCMessageBox::information(tr("Firmware Upgrade"), tr("Upgrade completed succesfully")); + _cancel(); + } else if (command == PX4FirmwareUpgradeThreadWorker::commandErase) { + _eraseTimer.stop(); + _appendStatusLog(tr("Flashing new firmware to board...")); + _threadController->program(_firmwareFilename); + } else if (command == PX4FirmwareUpgradeThreadWorker::commandCancel) { + // FIXME: This is no longer needed, no Cancel + if (_searchingForBoard) { + _appendStatusLog(tr("Board not found")); + _cancel(); + } else { + _appendStatusLog(tr("Bootloader not found")); + _cancel(); + } + } else { + Q_ASSERT(false); + } +} + +/// @brief Signals that an error has occured with the specified bootloader commands. Moves the state machine +/// to the appropriate error state. +void FirmwareUpgradeController::_error(const int command, const QString errorString) +{ + Q_UNUSED(command); + + _appendStatusLog(tr("Error: %1").arg(errorString)); + _cancel(); +} + +/// @brief Updates the progress bar from long running bootloader commands +void FirmwareUpgradeController::_updateProgress(int curr, int total) +{ + // FIXME: NYI + Q_UNUSED(curr); + Q_UNUSED(total); +// _ui->progressBar->setValue((curr*100) / total); +} + +/// @brief Resets the state machine back to the beginning +void FirmwareUpgradeController::_restart(void) +{ + // FIXME: NYI + //_setupState(upgradeStateBegin); +} + +/// @brief Moves the progress bar ahead on tick while erasing the board +void FirmwareUpgradeController::_eraseProgressTick(void) +{ + _eraseTickCount++; + // FIXME: NYI +// _ui->progressBar->setValue((_eraseTickCount*_eraseTickMsec*100) / _eraseTotalMsec); +} + +void FirmwareUpgradeController::doFirmwareUpgrade(void) +{ + Q_ASSERT(_upgradeButton); + _upgradeButton->setEnabled(false); + + _findBoard(); +} + +/// Appends the specified text to the status log area in the ui +void FirmwareUpgradeController::_appendStatusLog(const QString& text) +{ + Q_ASSERT(_statusLog); + + QVariant returnedValue; + QVariant varText = text; + QMetaObject::invokeMethod(_statusLog, + "append", + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, varText)); +} \ No newline at end of file diff --git a/src/VehicleSetup/FirmwareUpgradeController.h b/src/VehicleSetup/FirmwareUpgradeController.h new file mode 100644 index 0000000..3406aa0 --- /dev/null +++ b/src/VehicleSetup/FirmwareUpgradeController.h @@ -0,0 +1,148 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009, 2015 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +/// @file +/// @author Don Gagne + +#ifndef FirmwareUpgradeController_H +#define FirmwareUpgradeController_H + +#include "PX4FirmwareUpgradeThread.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "qextserialport.h" + +#include + +// Firmware Upgrade MVC Controller for FirmwareUpgrade.qml. +class FirmwareUpgradeController : public QObject +{ + Q_OBJECT + +public: + FirmwareUpgradeController(void); + + /// Supported firmware types + typedef enum { + StableFirmware, + BetaFirmware, + DeveloperFirmware, + CustomFirmware + } FirmwareType_t; + + Q_ENUMS(FirmwareType_t) + + /// Firmare type to load + Q_PROPERTY(FirmwareType_t firmwareType READ firmwareType WRITE setFirmwareType) + + /// Upgrade push button in UI + Q_PROPERTY(QQuickItem* upgradeButton READ upgradeButton WRITE setUpgradeButton) + + /// TextArea for log output + Q_PROPERTY(QQuickItem* statusLog READ statusLog WRITE setStatusLog) + + /// Begins the firware upgrade process + Q_INVOKABLE void doFirmwareUpgrade(void); + + FirmwareType_t firmwareType(void) { return _firmwareType; } + void setFirmwareType(FirmwareType_t firmwareType) { _firmwareType = firmwareType; } + + QQuickItem* upgradeButton(void) { return _upgradeButton; } + void setUpgradeButton(QQuickItem* upgradeButton) { _upgradeButton = upgradeButton; } + + QQuickItem* statusLog(void) { return _statusLog; } + void setStatusLog(QQuickItem* statusLog) { _statusLog = statusLog; } + +private slots: + void _downloadProgress(qint64 curr, qint64 total); + void _downloadFinished(void); + void _downloadError(QNetworkReply::NetworkError code); + void _foundBoard(bool firstTry, const QString portname, QString portDescription); + void _foundBootloader(int bootloaderVersion, int boardID, int flashSize); + void _error(const int command, const QString errorString); + void _bootloaderSyncFailed(void); + void _findTimeout(void); + void _complete(const int command); + void _updateProgress(int curr, int total); + void _restart(void); + void _eraseProgressTick(void); + +private: + void _findBoard(void); + void _findBootloader(void); + void _cancel(void); + void _getFirmwareFile(void); + + void _setBoardIcon(int boardID); + + void _downloadFirmware(void); + + void _erase(void); + + void _appendStatusLog(const QString& text); + + QString _portName; + QString _portDescription; + uint32_t _bootloaderVersion; + + static const int _boardIDPX4FMUV1 = 5; ///< Board ID for PX4 V1 board + static const int _boardIDPX4FMUV2 = 9; ///< Board ID for PX4 V2 board + static const int _boardIDPX4Flow = 6; ///< Board ID for PX4 Flow board + + uint32_t _boardID; ///< Board ID + uint32_t _boardFlashSize; ///< Flash size in bytes of board + uint32_t _imageSize; ///< Image size of firmware being flashed + + QPixmap _boardIcon; ///< Icon used to display image of board + + QString _firmwareFilename; ///< Image which we are going to flash to the board + + QNetworkAccessManager* _downloadManager; ///< Used for firmware file downloading across the internet + QNetworkReply* _downloadNetworkReply; ///< Used for firmware file downloading across the internet + + /// @brief Thread controller which is used to run bootloader commands on seperate thread + PX4FirmwareUpgradeThreadController* _threadController; + + static const int _eraseTickMsec = 500; ///< Progress bar update tick time for erase + static const int _eraseTotalMsec = 15000; ///< Estimated amount of time erase takes + int _eraseTickCount; ///< Number of ticks for erase progress update + QTimer _eraseTimer; ///< Timer used to update progress bar for erase + + static const int _findBoardTimeoutMsec = 30000; ///< Amount of time for user to plug in USB + static const int _findBootloaderTimeoutMsec = 5000; ///< Amount time to look for bootloader + + FirmwareType_t _firmwareType; ///< Firmware type to load + QQuickItem* _upgradeButton; ///< Upgrade button in ui + QQuickItem* _statusLog; ///< Status log TextArea Qml control + + bool _searchingForBoard; ///< true: searching for board, false: search for bootloader +}; + +#endif diff --git a/src/VehicleSetup/PX4Bootloader.cc b/src/VehicleSetup/PX4Bootloader.cc new file mode 100644 index 0000000..217bcca --- /dev/null +++ b/src/VehicleSetup/PX4Bootloader.cc @@ -0,0 +1,466 @@ +/*===================================================================== + + 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 . + + ======================================================================*/ + +/// @file +/// @brief PX4 Bootloader Utility routines +/// @author Don Gagne + +#include "PX4Bootloader.h" + +#include +#include +#include +#include + +#include "QGC.h" + +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 +}; + +static quint32 crc32(const uint8_t *src, unsigned len, unsigned state) +{ + for (unsigned i = 0; i < len; i++) { + state = crctab[(state ^ src[i]) & 0xff] ^ (state >> 8); + } + return state; +} + +PX4Bootloader::PX4Bootloader(QObject *parent) : + QObject(parent) +{ + +} + +bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxSize) +{ + qint64 bytesWritten = port->write((const char*)data, maxSize); + if (bytesWritten == -1) { + _errorString = tr("Write failed: %1").arg(port->errorString()); + qWarning() << _errorString; + return false; + } + if (bytesWritten != maxSize) { + _errorString = tr("Incorrect number of bytes returned for write: actual(%1) expected(%2)").arg(bytesWritten).arg(maxSize); + qWarning() << _errorString; + return false; + } + + return true; +} + +bool PX4Bootloader::write(QextSerialPort* port, const uint8_t byte) +{ + uint8_t buf[1] = { byte }; + return write(port, buf, 1); +} + +bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout) +{ + qint64 bytesAlreadyRead = 0; + + while (bytesAlreadyRead < maxSize) { + QTime timeout; + timeout.start(); + while (port->bytesAvailable() < 1) { + if (timeout.elapsed() > readTimeout) { + _errorString = tr("Timeout waiting for bytes to be available"); + return false; + } + QGC::SLEEP::usleep(100); + } + + qint64 bytesRead; + bytesRead = port->read((char*)&data[bytesAlreadyRead], maxSize); + + if (bytesRead == -1) { + _errorString = tr("Read failed: error: %1").arg(port->errorString()); + return false; + } else { + Q_ASSERT(bytesRead != 0); + bytesAlreadyRead += bytesRead; + } + } + + return true; +} + +bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout) +{ + uint8_t response[2]; + + if (!read(port, response, 2, responseTimeout)) { + _errorString.prepend("Get Command Response: "); + return false; + } + + // Make sure we get a good sync response + if (response[0] != PROTO_INSYNC) { + _errorString = tr("Invalid sync response: 0x%1 0x%2").arg(response[0], 2, 16, QLatin1Char('0')).arg(response[1], 2, 16, QLatin1Char('0')); + return false; + } else if (response[1] != PROTO_OK) { + QString responseCode = tr("Unknown response code"); + if (response[1] == PROTO_FAILED) { + responseCode = "PROTO_FAILED"; + } else if (response[1] == PROTO_INVALID) { + responseCode = "PROTO_INVALID"; + } + _errorString = tr("Command failed: 0x%1 (%2)").arg(response[1], 2, 16, QLatin1Char('0')).arg(responseCode); + return false; + } + + return true; +} + +bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value) +{ + uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC }; + + if (!write(port, buf, sizeof(buf))) { + goto Error; + } + port->flush(); + if (!read(port, (uint8_t*)&value, sizeof(value))) { + goto Error; + } + if (!getCommandResponse(port)) { + goto Error; + } + + return true; + +Error: + _errorString.prepend("Get Board Info: "); + return false; +} + +bool PX4Bootloader::sendCommand(QextSerialPort* port, const uint8_t cmd, int responseTimeout) +{ + uint8_t buf[2] = { cmd, PROTO_EOC }; + + if (!write(port, buf, 2)) { + goto Error; + } + port->flush(); + if (!getCommandResponse(port, responseTimeout)) { + goto Error; + } + + return true; + +Error: + _errorString.prepend("Send Command: "); + return false; +} + +bool PX4Bootloader::erase(QextSerialPort* port) +{ + // Erase is slow, need larger timeout + if (!sendCommand(port, PROTO_CHIP_ERASE, _eraseTimeout)) { + _errorString = tr("Board erase failed: %1").arg(_errorString); + return false; + } + + return true; +} + +bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilename) +{ + QFile firmwareFile(firmwareFilename); + if (!firmwareFile.open(QIODevice::ReadOnly)) { + _errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString()); + return false; + } + uint32_t imageSize = (uint32_t)firmwareFile.size(); + + uint8_t imageBuf[PROG_MULTI_MAX]; + uint32_t bytesSent = 0; + _imageCRC = 0; + + Q_ASSERT(PROG_MULTI_MAX <= 0x8F); + + while (bytesSent < imageSize) { + int bytesToSend = imageSize - bytesSent; + if (bytesToSend > (int)sizeof(imageBuf)) { + bytesToSend = (int)sizeof(imageBuf); + } + + Q_ASSERT((bytesToSend % 4) == 0); + + int bytesRead = firmwareFile.read((char *)imageBuf, bytesToSend); + if (bytesRead == -1 || bytesRead != bytesToSend) { + _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); + return false; + } + + Q_ASSERT(bytesToSend <= 0x8F); + + bool failed = true; + if (write(port, PROTO_PROG_MULTI)) { + if (write(port, (uint8_t)bytesToSend)) { + if (write(port, imageBuf, bytesToSend)) { + if (write(port, PROTO_EOC)) { + port->flush(); + if (getCommandResponse(port)) { + failed = false; + } + } + } + } + } + if (failed) { + _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(bytesSent, 8, 16, QLatin1Char('0')); + return false; + } + + bytesSent += bytesToSend; + + // Calculate the CRC now so we can test it after the board is flashed. + _imageCRC = crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC); + + emit updateProgramProgress(bytesSent, imageSize); + } + firmwareFile.close(); + + // We calculate the CRC using the entire flash size, filling the remainder with 0xFF. + while (bytesSent < _boardFlashSize) { + const uint8_t fill = 0xFF; + _imageCRC = crc32(&fill, 1, _imageCRC); + bytesSent++; + } + + return true; +} + +bool PX4Bootloader::verify(QextSerialPort* port, const QString firmwareFilename) +{ + bool ret; + + if (_bootloaderVersion <= 2) { + ret = _bootloaderVerifyRev2(port, firmwareFilename); + } else { + ret = _bootloaderVerifyRev3(port); + } + + sendBootloaderReboot(port); + + return ret; +} + +/// @brief Verify the flash on bootloader version 2 by reading it back and comparing it against +/// the original firmware file. +bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString firmwareFilename) +{ + QFile firmwareFile(firmwareFilename); + if (!firmwareFile.open(QIODevice::ReadOnly)) { + _errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString()); + return false; + } + uint32_t imageSize = (uint32_t)firmwareFile.size(); + + if (!sendCommand(port, PROTO_CHIP_VERIFY)) { + return false; + } + + uint8_t fileBuf[READ_MULTI_MAX]; + uint8_t flashBuf[READ_MULTI_MAX]; + uint32_t bytesVerified = 0; + + Q_ASSERT(PROG_MULTI_MAX <= 0x8F); + + while (bytesVerified < imageSize) { + int bytesToRead = imageSize - bytesVerified; + if (bytesToRead > (int)sizeof(fileBuf)) { + bytesToRead = (int)sizeof(fileBuf); + } + + Q_ASSERT((bytesToRead % 4) == 0); + + int bytesRead = firmwareFile.read((char *)fileBuf, bytesToRead); + if (bytesRead == -1 || bytesRead != bytesToRead) { + _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); + return false; + } + + Q_ASSERT(bytesToRead <= 0x8F); + + bool failed = true; + if (write(port, PROTO_READ_MULTI)) { + if (write(port, (uint8_t)bytesToRead)) { + if (write(port, PROTO_EOC)) { + port->flush(); + if (read(port, flashBuf, sizeof(flashBuf))) { + if (getCommandResponse(port)) { + failed = false; + } + } + } + } + } + if (failed) { + _errorString = tr("Verify failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0')); + return false; + } + + for (int i=0; iflush(); + if (read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) { + if (getCommandResponse(port)) { + failed = false; + } + } + } + if (failed) { + return false; + } + + if (_imageCRC != flashCRC) { + _errorString = tr("CRC mismatch: board(0x%1) file(0x%2)").arg(flashCRC, 4, 16, QLatin1Char('0')).arg(_imageCRC, 4, 16, QLatin1Char('0')); + return false; + } + + return true; +} + +bool PX4Bootloader::open(QextSerialPort* port, const QString portName) +{ + Q_ASSERT(!port->isOpen()); + + port->setPortName(portName); + port->setBaudRate(BAUD115200); + port->setDataBits(DATA_8); + port->setParity(PAR_NONE); + port->setStopBits(STOP_1); + port->setFlowControl(FLOW_OFF); + + if (!port->open(QIODevice::ReadWrite | QIODevice::Unbuffered)) { + _errorString = tr("Open failed on port %1: %2").arg(portName).arg(port->errorString()); + return false; + } + + return true; +} + +bool PX4Bootloader::sync(QextSerialPort* port) +{ + // Send sync command + if (sendCommand(port, PROTO_GET_SYNC)) { + return true; + } else { + _errorString.prepend("Sync: "); + return false; + } +} + +bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize) +{ + + if (!getBoardInfo(port, INFO_BL_REV, _bootloaderVersion)) { + goto Error; + } + if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) { + _errorString = tr("Found unsupported bootloader version: %1").arg(_bootloaderVersion); + goto Error; + } + + if (!getBoardInfo(port, INFO_BOARD_ID, _boardID)) { + goto Error; + } + if (_boardID != _boardIDPX4Flow && _boardID != _boardIDPX4FMUV1 && _boardID != _boardIDPX4FMUV2) { + _errorString = tr("Unsupported board: %1").arg(_boardID); + goto Error; + } + + if (!getBoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) { + qWarning() << _errorString; + goto Error; + } + + bootloaderVersion = _bootloaderVersion; + boardID = _boardID; + flashSize = _boardFlashSize; + + return true; + +Error: + _errorString.prepend("Get Board Info: "); + return false; +} + +bool PX4Bootloader::sendBootloaderReboot(QextSerialPort* port) +{ + return write(port, PROTO_BOOT) && write(port, PROTO_EOC); +} diff --git a/src/VehicleSetup/PX4Bootloader.h b/src/VehicleSetup/PX4Bootloader.h new file mode 100644 index 0000000..2587c6a --- /dev/null +++ b/src/VehicleSetup/PX4Bootloader.h @@ -0,0 +1,165 @@ +/*===================================================================== + + 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 . + + ======================================================================*/ + +/// @file +/// @brief PX4 Bootloader Utility routines +/// @author Don Gagne + +#ifndef PX4Bootloader_H +#define PX4Bootloader_H + +#include "qextserialport.h" + +#include + +/// @brief This class is used to communicate with the Bootloader. +class PX4Bootloader : public QObject +{ + Q_OBJECT + +public: + explicit PX4Bootloader(QObject *parent = 0); + + /// @brief Returns the error message associated with the last failed call to one of the bootloader + /// utility routine below. + QString errorString(void) { return _errorString; } + + /// @brief Write a byte to the port + /// @param port Port to write to + /// @param data Bytes to write + /// @param maxSize Number of bytes to write + /// @return true: success + bool write(QextSerialPort* port, const uint8_t* data, qint64 maxSize); + bool write(QextSerialPort* port, const uint8_t byte); + + /// @brief Read a set of bytes from the port + /// @param data Read bytes into this buffer + /// @param maxSize Number of bytes to read + /// @param readTimeout Msecs to wait for bytes to become available on port + bool read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout = _readTimout); + + /// @brief Read a PROTO_SYNC command response from the bootloader + /// @param responseTimeout Msecs to wait for response bytes to become available on port + bool getCommandResponse(QextSerialPort* port, const int responseTimeout = _responseTimeout); + + /// @brief Send a PROTO_GET_DEVICE command to retrieve a value from the bootloader + /// @param param Value to retrieve using INFO_BOARD_* enums + /// @param value Returned value + bool getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value); + + /// @brief Send a command to the bootloader + /// @param cmd Command to send using PROTO_* enums + /// @return true: Command sent and valid sync response returned + bool sendCommand(QextSerialPort* port, uint8_t cmd, int responseTimeout = _responseTimeout); + + /// @brief Program the board with the specified firmware + bool program(QextSerialPort* port, const QString& firmwareFilename); + + /// @brief Verify the board flash. How it works depend on bootloader rev + /// Rev 2: Read the flash back and compare it against the firmware file + /// Rev 3: Compare CRCs for flash and file + bool verify(QextSerialPort* port, const QString firmwareFilename); + + /// @brief Read a PROTO_SYNC response from the bootloader + /// @return true: Valid sync response was received + bool sync(QextSerialPort* port); + + /// @brief Retrieve a set of board info from the bootloader + /// @param bootloaderVersion Returned INFO_BL_REV + /// @param boardID Returned INFO_BOARD_ID + /// @param flashSize Returned INFO_FLASH_SIZE + bool getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize); + + /// @brief Opens a port to the bootloader + bool open(QextSerialPort* port, const QString portName); + + /// @brief Sends a PROTO_REBOOT command to the bootloader + bool sendBootloaderReboot(QextSerialPort* port); + + /// @brief Sends a PROTO_ERASE command to the bootlader + bool erase(QextSerialPort* port); + +signals: + /// @brief Signals progress indicator for long running bootloader utility routines + void updateProgramProgress(int curr, int total); + +private: + enum { + // protocol bytes + PROTO_INSYNC = 0x12, ///< 'in sync' byte sent before status + PROTO_EOC = 0x20, ///< end of command + + // Reply bytes + PROTO_OK = 0x10, ///< INSYNC/OK - 'ok' response + PROTO_FAILED = 0x11, ///< INSYNC/FAILED - 'fail' response + PROTO_INVALID = 0x13, ///< INSYNC/INVALID - 'invalid' response for bad commands + + // Command bytes + PROTO_GET_SYNC = 0x21, ///< NOP for re-establishing sync + PROTO_GET_DEVICE = 0x22, ///< get device ID bytes + PROTO_CHIP_ERASE = 0x23, ///< erase program area and reset program address + PROTO_PROG_MULTI = 0x27, ///< write bytes at program address and increment + PROTO_GET_CRC = 0x29, ///< compute & return a CRC + PROTO_BOOT = 0x30, ///< boot the application + + // Command bytes - Rev 2 boootloader only + PROTO_CHIP_VERIFY = 0x24, ///< begin verify mode + PROTO_READ_MULTI = 0x28, ///< read bytes at programm address and increment + + INFO_BL_REV = 1, ///< bootloader protocol revision + BL_REV_MIN = 2, ///< Minimum supported bootlader protocol + BL_REV_MAX = 4, ///< Maximum supported bootloader protocol + INFO_BOARD_ID = 2, ///< board type + INFO_BOARD_REV = 3, ///< board revision + INFO_FLASH_SIZE = 4, ///< max firmware size in bytes + + PROG_MULTI_MAX = 32, ///< write size for PROTO_PROG_MULTI, must be multiple of 4 + READ_MULTI_MAX = 32 ///< read size for PROTO_READ_MULTI, must be multiple of 4 + }; + + bool _findBootloader(void); + bool _downloadFirmware(void); + bool _bootloaderVerifyRev2(QextSerialPort* port, const QString firmwareFilename); + bool _bootloaderVerifyRev3(QextSerialPort* port); + + static const int _boardIDPX4FMUV1 = 5; ///< Board ID for PX4 V1 board + static const int _boardIDPX4FMUV2 = 9; ///< Board ID for PX4 V2 board + static const int _boardIDPX4Flow = 6; ///< Board ID for PX4 Floaw board + + uint32_t _boardID; ///< board id for currently connected board + uint32_t _boardFlashSize; ///< flash size for currently connected board + uint32_t _imageCRC; ///< CRC for image in currently selected firmware file + uint32_t _bootloaderVersion; ///< Bootloader version + + QString _firmwareFilename; ///< Currently selected firmware file to flash + + QString _errorString; ///< Last error + + static const int _eraseTimeout = 20000; ///< Msecs to wait for response from erase command + static const int _rebootTimeout = 10000; ///< Msecs to wait for reboot command to cause serial port to disconnect + static const int _verifyTimeout = 5000; ///< Msecs to wait for response to PROTO_GET_CRC command + static const int _readTimout = 2000; ///< Msecs to wait for read bytes to become avilable + static const int _responseTimeout = 2000; ///< Msecs to wait for command response bytes +}; + +#endif // PX4FirmwareUpgrade_H diff --git a/src/VehicleSetup/PX4FirmwareUpgradeThread.cc b/src/VehicleSetup/PX4FirmwareUpgradeThread.cc new file mode 100644 index 0000000..784948e --- /dev/null +++ b/src/VehicleSetup/PX4FirmwareUpgradeThread.cc @@ -0,0 +1,296 @@ +/*===================================================================== + + 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 . + + ======================================================================*/ + +/// @file +/// @brief PX4 Firmware Upgrade operations which occur on a seperate thread. +/// @author Don Gagne + +#include "PX4FirmwareUpgradeThread.h" +#include "PX4Bootloader.h" + +#include +#include +#include + +PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(QObject* parent) : + QObject(parent), + _bootloader(NULL), + _bootloaderPort(NULL), + _timerTimeout(NULL), + _timerRetry(NULL) +{ + +} + +PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker() +{ + if (_bootloaderPort) { + // deleteLater so delete happens on correct thread + _bootloaderPort->deleteLater(); + } +} + +/// @brief Initializes the PX4FirmwareUpgradeThreadWorker with the various child objects which must be created +/// on the worker thread. +void PX4FirmwareUpgradeThreadWorker::init(void) +{ + // We create the timers here so that they are on the right thread + + Q_ASSERT(_timerTimeout == NULL); + _timerTimeout = new QTimer(this); + Q_CHECK_PTR(_timerTimeout); + connect(_timerTimeout, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::timeout); + _timerTimeout->setSingleShot(true); + + Q_ASSERT(_timerRetry == NULL); + _timerRetry = new QTimer(this); + Q_CHECK_PTR(_timerRetry); + _timerRetry->setSingleShot(true); + _timerRetry->setInterval(_retryTimeout); + + Q_ASSERT(_bootloader == NULL); + _bootloader = new PX4Bootloader(this); + connect(_bootloader, &PX4Bootloader::updateProgramProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgramProgress); +} + +void PX4FirmwareUpgradeThreadWorker::findBoard(int msecTimeout) +{ + _findBoardFirstAttempt = true; + connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce); + _timerTimeout->start(msecTimeout); + _elapsed.start(); + _findBoardOnce(); +} + +void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void) +{ + qDebug() << "_findBoardOnce"; + + QString portName; + QString portDescription; + + foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) { + if (!info.portName().isEmpty() && (info.description().contains("PX4") || info.vendorIdentifier() == 9900 /* 3DR */)) { + + qDebug() << "Found Board:"; + qDebug() << "\tport name:" << info.portName(); + qDebug() << "\tdescription:" << info.description(); + qDebug() << "\tsystem location:" << info.systemLocation(); + qDebug() << "\tvendor ID:" << info.vendorIdentifier(); + qDebug() << "\tproduct ID:" << info.productIdentifier(); + + portName = info.systemLocation(); + portDescription = info.description(); + + _closeFind(); + emit foundBoard(_findBoardFirstAttempt, portName, portDescription); + return; + } + } + + _findBoardFirstAttempt = false; + + emit updateProgress(_elapsed.elapsed(), _timerTimeout->interval()); + _timerRetry->start(); +} + +void PX4FirmwareUpgradeThreadWorker::findBootloader(const QString portName, int msecTimeout) +{ + Q_UNUSED(msecTimeout); + + // Once the port shows up, we only try to connect to the bootloader a single time + _portName = portName; + _findBootloaderOnce(); +} + +void PX4FirmwareUpgradeThreadWorker::_findBootloaderOnce(void) +{ + qDebug() << "_findBootloaderOnce"; + + uint32_t bootloaderVersion, boardID, flashSize; + + _bootloaderPort = new QextSerialPort(QextSerialPort::Polling); + Q_CHECK_PTR(_bootloaderPort); + + if (_bootloader->open(_bootloaderPort, _portName)) { + if (_bootloader->sync(_bootloaderPort)) { + if (_bootloader->getBoardInfo(_bootloaderPort, bootloaderVersion, boardID, flashSize)) { + _closeFind(); + qDebug() << "Found bootloader"; + emit foundBootloader(bootloaderVersion, boardID, flashSize); + return; + } + } + } + + _closeFind(); + _bootloaderPort->close(); + _bootloaderPort->deleteLater(); + _bootloaderPort = NULL; + qDebug() << "Bootloader error:" << _bootloader->errorString(); + emit error(commandBootloader, _bootloader->errorString()); +} + +void PX4FirmwareUpgradeThreadWorker::_closeFind(void) +{ + emit updateProgress(100, 100); + disconnect(_timerRetry, SIGNAL(timeout()), 0, 0); + _timerRetry->stop(); + _timerTimeout->stop(); +} + +void PX4FirmwareUpgradeThreadWorker::cancelFind(void) +{ + _closeFind(); + emit complete(commandCancel); +} + +void PX4FirmwareUpgradeThreadWorker::timeout(void) +{ + qDebug() << "Find timeout"; + _closeFind(); + emit findTimeout(); +} + +void PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot(void) +{ + if (_bootloaderPort) { + if (_bootloaderPort->isOpen()) { + _bootloader->sendBootloaderReboot(_bootloaderPort); + } + _bootloaderPort->deleteLater(); + _bootloaderPort = NULL; + } +} + +void PX4FirmwareUpgradeThreadWorker::program(const QString firmwareFilename) +{ + qDebug() << "Program"; + if (!_bootloader->program(_bootloaderPort, firmwareFilename)) { + _bootloaderPort->deleteLater(); + _bootloaderPort = NULL; + qDebug() << "Program failed:" << _bootloader->errorString(); + emit error(commandProgram, _bootloader->errorString()); + } else { + qDebug() << "Program complete"; + emit complete(commandProgram); + } +} + +void PX4FirmwareUpgradeThreadWorker::verify(const QString firmwareFilename) +{ + qDebug() << "Verify"; + if (!_bootloader->verify(_bootloaderPort, firmwareFilename)) { + qDebug() << "Verify failed:" << _bootloader->errorString(); + emit error(commandVerify, _bootloader->errorString()); + } else { + qDebug() << "Verify complete"; + emit complete(commandVerify); + } + _bootloaderPort->deleteLater(); + _bootloaderPort = NULL; +} + +void PX4FirmwareUpgradeThreadWorker::erase(void) +{ + qDebug() << "Erase"; + if (!_bootloader->erase(_bootloaderPort)) { + _bootloaderPort->deleteLater(); + _bootloaderPort = NULL; + qDebug() << "Erase failed:" << _bootloader->errorString(); + emit error(commandErase, _bootloader->errorString()); + } else { + qDebug() << "Erase complete"; + emit complete(commandErase); + } +} + +PX4FirmwareUpgradeThreadController::PX4FirmwareUpgradeThreadController(QObject* parent) : + QObject(parent) +{ + _worker = new PX4FirmwareUpgradeThreadWorker(); + Q_CHECK_PTR(_worker); + + _workerThread = new QThread(this); + Q_CHECK_PTR(_workerThread); + _worker->moveToThread(_workerThread); + + connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoard, this, &PX4FirmwareUpgradeThreadController::_foundBoard); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBootloader, this, &PX4FirmwareUpgradeThreadController::_foundBootloader); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::bootloaderSyncFailed, this, &PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::complete, this, &PX4FirmwareUpgradeThreadController::_complete); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::findTimeout, this, &PX4FirmwareUpgradeThreadController::_findTimeout); + connect(_worker, &PX4FirmwareUpgradeThreadWorker::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress); + + connect(this, &PX4FirmwareUpgradeThreadController::_initThreadWorker, _worker, &PX4FirmwareUpgradeThreadWorker::init); + connect(this, &PX4FirmwareUpgradeThreadController::_findBoardOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::findBoard); + connect(this, &PX4FirmwareUpgradeThreadController::_findBootloaderOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::findBootloader); + connect(this, &PX4FirmwareUpgradeThreadController::_sendBootloaderRebootOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot); + connect(this, &PX4FirmwareUpgradeThreadController::_programOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::program); + connect(this, &PX4FirmwareUpgradeThreadController::_verifyOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::verify); + connect(this, &PX4FirmwareUpgradeThreadController::_eraseOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::erase); + connect(this, &PX4FirmwareUpgradeThreadController::_cancelFindOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::cancelFind); + + _workerThread->start(); + + emit _initThreadWorker(); +} + +PX4FirmwareUpgradeThreadController::~PX4FirmwareUpgradeThreadController() +{ + _workerThread->quit(); + _workerThread->wait(); +} + +void PX4FirmwareUpgradeThreadController::findBoard(int msecTimeout) +{ + qDebug() << "PX4FirmwareUpgradeThreadController::findBoard"; + emit _findBoardOnThread(msecTimeout); +} + +void PX4FirmwareUpgradeThreadController::findBootloader(const QString& portName, int msecTimeout) +{ + qDebug() << "PX4FirmwareUpgradeThreadController::findBootloader"; + emit _findBootloaderOnThread(portName, msecTimeout); +} + +void PX4FirmwareUpgradeThreadController::_foundBoard(bool firstTry, const QString portName, QString portDescription) +{ + emit foundBoard(firstTry, portName, portDescription); +} + +void PX4FirmwareUpgradeThreadController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize) +{ + emit foundBootloader(bootloaderVersion, boardID, flashSize); +} + +void PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed(void) +{ + emit bootloaderSyncFailed(); +} + +void PX4FirmwareUpgradeThreadController::_findTimeout(void) +{ + emit findTimeout(); +} diff --git a/src/VehicleSetup/PX4FirmwareUpgradeThread.h b/src/VehicleSetup/PX4FirmwareUpgradeThread.h new file mode 100644 index 0000000..141c130 --- /dev/null +++ b/src/VehicleSetup/PX4FirmwareUpgradeThread.h @@ -0,0 +1,183 @@ +/*===================================================================== + + 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 . + + ======================================================================*/ + +/// @file +/// @brief PX4 Firmware Upgrade operations which occur on a seperate thread. +/// @author Don Gagne + +#ifndef PX4FirmwareUpgradeThread_H +#define PX4FirmwareUpgradeThread_H + +#include +#include +#include +#include + +#include "qextserialport.h" + +#include + +#include "PX4Bootloader.h" + +/// @brief Used to run bootloader commands on a seperate thread. These routines are mainly meant to to be called +/// internally by the PX4FirmwareUpgradeThreadController. Clients should call the various public methods +/// exposed by PX4FirmwareUpgradeThreadController. +class PX4FirmwareUpgradeThreadWorker : public QObject +{ + Q_OBJECT + +public: + PX4FirmwareUpgradeThreadWorker(QObject* parent = NULL); + ~PX4FirmwareUpgradeThreadWorker(); + + enum { + commandBootloader, + commandProgram, + commandVerify, + commandErase, + commandCancel + }; + +public slots: + void init(void); + void findBoard(int msecTimeout); + void findBootloader(const QString portName, int msecTimeout); + void timeout(void); + void cancelFind(void); + void sendBootloaderReboot(void); + void program(const QString firmwareFilename); + void verify(const QString firmwareFilename); + void erase(void); + +signals: + void foundBoard(bool firstTry, const QString portname, QString portDescription); + void foundBootloader(int bootloaderVersion, int boardID, int flashSize); + void bootloaderSyncFailed(void); + void error(const int command, const QString errorString); + void complete(const int command); + void findTimeout(void); + void updateProgress(int curr, int total); + +private slots: + void _findBoardOnce(void); + void _findBootloaderOnce(void); + void _updateProgramProgress(int curr, int total) { emit updateProgress(curr, total); } + void _closeFind(void); + +private: + PX4Bootloader* _bootloader; + QextSerialPort* _bootloaderPort; + QTimer* _timerTimeout; + QTimer* _timerRetry; + QTime _elapsed; + QString _portName; + static const int _retryTimeout = 1000; + bool _findBoardFirstAttempt; +}; + +/// @brief Provides methods to interact with the bootloader. The commands themselves are signalled +/// across to PX4FirmwareUpgradeThreadWorker so that they run on the seperate thread. +class PX4FirmwareUpgradeThreadController : public QObject +{ + Q_OBJECT + +public: + PX4FirmwareUpgradeThreadController(QObject* parent = NULL); + ~PX4FirmwareUpgradeThreadController(void); + + /// @brief Begins the process of searching for a PX4 board connected to any serial port. + /// @param msecTimeout Numbers of msecs to continue looking for a board to become available. + void findBoard(int msecTimeout); + + /// @brief Begins the process of attempting to communicate with the bootloader on the specified port. + /// @param portName Name of port to attempt a bootloader connection on. + /// @param msecTimeout Number of msecs to continue to wait for a bootloader to appear on the port. + void findBootloader(const QString& portName, int msecTimeout); + + /// @brief Cancel an in progress findBoard or FindBootloader + void cancelFind(void) { emit _cancelFindOnThread(); } + + /// @brief Sends a reboot command to the bootloader + void sendBootloaderReboot(void) { emit _sendBootloaderRebootOnThread(); } + + /// @brief Flash the specified firmware onto the board + void program(const QString firmwareFilename) { emit _programOnThread(firmwareFilename); } + + /// @brief Verify the board flash with respect to the specified firmware image + void verify(const QString firmwareFilename) { emit _verifyOnThread(firmwareFilename); } + + /// @brief Send and erase command to the bootloader + void erase(void) { emit _eraseOnThread(); } + +signals: + /// @brief Emitted by the findBoard process when it finds the board. + /// @param firstTry true: board found on first attempt + /// @param portName Port that board is on + /// @param portDescription User friendly port description + void foundBoard(bool firstTry, const QString portname, QString portDescription); + + /// @brief Emitted by the findBootloader process when has a connection to the bootloader + void foundBootloader(int bootloaderVersion, int boardID, int flashSize); + + /// @brief Emitted by the bootloader commands when an error occurs. + /// @param errorCommand Command which caused the error, using PX4FirmwareUpgradeThreadWorker command* enum values + void error(const int errorCommand, const QString errorString); + + /// @brief Signalled when the findBootloader process connects to the port, but cannot sync to the + /// bootloader. + void bootloaderSyncFailed(void); + + /// @brief Signalled when the findBoard or findBootloader process times out before success + void findTimeout(void); + + /// @brief Signalled by the bootloader commands other than find* that they have complete successfully. + /// @param command Command which completed, using PX4FirmwareUpgradeThreadWorker command* enum values + void complete(const int command); + + /// @brief Signalled to update progress for long running bootloader commands + void updateProgress(int curr, int total); + + void _initThreadWorker(void); + void _findBoardOnThread(int msecTimeout); + void _findBootloaderOnThread(const QString& portName, int msecTimeout); + void _sendBootloaderRebootOnThread(void); + void _programOnThread(const QString firmwareFilename); + void _verifyOnThread(const QString firmwareFilename); + void _eraseOnThread(void); + void _cancelFindOnThread(void); + +private slots: + void _foundBoard(bool firstTry, const QString portname, QString portDescription); + void _foundBootloader(int bootloaderVersion, int boardID, int flashSize); + void _bootloaderSyncFailed(void); + void _error(const int errorCommand, const QString errorString) { emit error(errorCommand, errorString); } + void _complete(const int command) { emit complete(command); } + void _findTimeout(void); + void _updateProgress(int curr, int total) { emit updateProgress(curr, total); } + +private: + PX4FirmwareUpgradeThreadWorker* _worker; + QThread* _workerThread; ///< Thread which PX4FirmwareUpgradeThreadWorker runs on +}; + +#endif diff --git a/src/VehicleSetup/SetupView.cc b/src/VehicleSetup/SetupView.cc index 33a02f4..7d24474 100644 --- a/src/VehicleSetup/SetupView.cc +++ b/src/VehicleSetup/SetupView.cc @@ -30,11 +30,11 @@ #include "UASManager.h" #include "AutoPilotPluginManager.h" #include "VehicleComponent.h" -#include "PX4FirmwareUpgrade.h" #include "ParameterEditor.h" #include "QGCQmlWidgetHolder.h" #include "MainWindow.h" #include "QGCMessageBox.h" +#include "FirmwareUpgradeController.h" #include #include @@ -54,13 +54,13 @@ SetupView::SetupView(QWidget* parent) : Q_UNUSED(fSucceeded); Q_ASSERT(fSucceeded); - //setResizeMode(SizeRootObjectToView); - _ui->buttonHolder->setAutoPilot(NULL); _ui->buttonHolder->setSource(QUrl::fromUserInput("qrc:/qml/SetupViewButtons.qml")); _ui->buttonHolder->rootContext()->setContextProperty("controller", this); + qmlRegisterType("QGroundControl.FirmwareUpgradeController", 1, 0, "FirmwareUpgradeController"); + _setActiveUAS(UASManager::instance()->getActiveUAS()); } @@ -120,16 +120,11 @@ void SetupView::firmwareButtonClicked(void) return; } -#if 1 - PX4FirmwareUpgrade* setup = new PX4FirmwareUpgrade(this); -#else - // NYI QGCQmlWidgetHolder* setup = new QGCQmlWidgetHolder; Q_CHECK_PTR(setup); - //setup->setAutoPilot(_autoPilotPlugin); setup->setSource(QUrl::fromUserInput("qrc:/qml/FirmwareUpgrade.qml")); -#endif + _changeSetupWidget(setup); } diff --git a/src/ui/px4_configuration/PX4Bootloader.cc b/src/ui/px4_configuration/PX4Bootloader.cc deleted file mode 100644 index 217bcca..0000000 --- a/src/ui/px4_configuration/PX4Bootloader.cc +++ /dev/null @@ -1,466 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Bootloader Utility routines -/// @author Don Gagne - -#include "PX4Bootloader.h" - -#include -#include -#include -#include - -#include "QGC.h" - -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 -}; - -static quint32 crc32(const uint8_t *src, unsigned len, unsigned state) -{ - for (unsigned i = 0; i < len; i++) { - state = crctab[(state ^ src[i]) & 0xff] ^ (state >> 8); - } - return state; -} - -PX4Bootloader::PX4Bootloader(QObject *parent) : - QObject(parent) -{ - -} - -bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxSize) -{ - qint64 bytesWritten = port->write((const char*)data, maxSize); - if (bytesWritten == -1) { - _errorString = tr("Write failed: %1").arg(port->errorString()); - qWarning() << _errorString; - return false; - } - if (bytesWritten != maxSize) { - _errorString = tr("Incorrect number of bytes returned for write: actual(%1) expected(%2)").arg(bytesWritten).arg(maxSize); - qWarning() << _errorString; - return false; - } - - return true; -} - -bool PX4Bootloader::write(QextSerialPort* port, const uint8_t byte) -{ - uint8_t buf[1] = { byte }; - return write(port, buf, 1); -} - -bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout) -{ - qint64 bytesAlreadyRead = 0; - - while (bytesAlreadyRead < maxSize) { - QTime timeout; - timeout.start(); - while (port->bytesAvailable() < 1) { - if (timeout.elapsed() > readTimeout) { - _errorString = tr("Timeout waiting for bytes to be available"); - return false; - } - QGC::SLEEP::usleep(100); - } - - qint64 bytesRead; - bytesRead = port->read((char*)&data[bytesAlreadyRead], maxSize); - - if (bytesRead == -1) { - _errorString = tr("Read failed: error: %1").arg(port->errorString()); - return false; - } else { - Q_ASSERT(bytesRead != 0); - bytesAlreadyRead += bytesRead; - } - } - - return true; -} - -bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout) -{ - uint8_t response[2]; - - if (!read(port, response, 2, responseTimeout)) { - _errorString.prepend("Get Command Response: "); - return false; - } - - // Make sure we get a good sync response - if (response[0] != PROTO_INSYNC) { - _errorString = tr("Invalid sync response: 0x%1 0x%2").arg(response[0], 2, 16, QLatin1Char('0')).arg(response[1], 2, 16, QLatin1Char('0')); - return false; - } else if (response[1] != PROTO_OK) { - QString responseCode = tr("Unknown response code"); - if (response[1] == PROTO_FAILED) { - responseCode = "PROTO_FAILED"; - } else if (response[1] == PROTO_INVALID) { - responseCode = "PROTO_INVALID"; - } - _errorString = tr("Command failed: 0x%1 (%2)").arg(response[1], 2, 16, QLatin1Char('0')).arg(responseCode); - return false; - } - - return true; -} - -bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value) -{ - uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC }; - - if (!write(port, buf, sizeof(buf))) { - goto Error; - } - port->flush(); - if (!read(port, (uint8_t*)&value, sizeof(value))) { - goto Error; - } - if (!getCommandResponse(port)) { - goto Error; - } - - return true; - -Error: - _errorString.prepend("Get Board Info: "); - return false; -} - -bool PX4Bootloader::sendCommand(QextSerialPort* port, const uint8_t cmd, int responseTimeout) -{ - uint8_t buf[2] = { cmd, PROTO_EOC }; - - if (!write(port, buf, 2)) { - goto Error; - } - port->flush(); - if (!getCommandResponse(port, responseTimeout)) { - goto Error; - } - - return true; - -Error: - _errorString.prepend("Send Command: "); - return false; -} - -bool PX4Bootloader::erase(QextSerialPort* port) -{ - // Erase is slow, need larger timeout - if (!sendCommand(port, PROTO_CHIP_ERASE, _eraseTimeout)) { - _errorString = tr("Board erase failed: %1").arg(_errorString); - return false; - } - - return true; -} - -bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilename) -{ - QFile firmwareFile(firmwareFilename); - if (!firmwareFile.open(QIODevice::ReadOnly)) { - _errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString()); - return false; - } - uint32_t imageSize = (uint32_t)firmwareFile.size(); - - uint8_t imageBuf[PROG_MULTI_MAX]; - uint32_t bytesSent = 0; - _imageCRC = 0; - - Q_ASSERT(PROG_MULTI_MAX <= 0x8F); - - while (bytesSent < imageSize) { - int bytesToSend = imageSize - bytesSent; - if (bytesToSend > (int)sizeof(imageBuf)) { - bytesToSend = (int)sizeof(imageBuf); - } - - Q_ASSERT((bytesToSend % 4) == 0); - - int bytesRead = firmwareFile.read((char *)imageBuf, bytesToSend); - if (bytesRead == -1 || bytesRead != bytesToSend) { - _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); - return false; - } - - Q_ASSERT(bytesToSend <= 0x8F); - - bool failed = true; - if (write(port, PROTO_PROG_MULTI)) { - if (write(port, (uint8_t)bytesToSend)) { - if (write(port, imageBuf, bytesToSend)) { - if (write(port, PROTO_EOC)) { - port->flush(); - if (getCommandResponse(port)) { - failed = false; - } - } - } - } - } - if (failed) { - _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(bytesSent, 8, 16, QLatin1Char('0')); - return false; - } - - bytesSent += bytesToSend; - - // Calculate the CRC now so we can test it after the board is flashed. - _imageCRC = crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC); - - emit updateProgramProgress(bytesSent, imageSize); - } - firmwareFile.close(); - - // We calculate the CRC using the entire flash size, filling the remainder with 0xFF. - while (bytesSent < _boardFlashSize) { - const uint8_t fill = 0xFF; - _imageCRC = crc32(&fill, 1, _imageCRC); - bytesSent++; - } - - return true; -} - -bool PX4Bootloader::verify(QextSerialPort* port, const QString firmwareFilename) -{ - bool ret; - - if (_bootloaderVersion <= 2) { - ret = _bootloaderVerifyRev2(port, firmwareFilename); - } else { - ret = _bootloaderVerifyRev3(port); - } - - sendBootloaderReboot(port); - - return ret; -} - -/// @brief Verify the flash on bootloader version 2 by reading it back and comparing it against -/// the original firmware file. -bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString firmwareFilename) -{ - QFile firmwareFile(firmwareFilename); - if (!firmwareFile.open(QIODevice::ReadOnly)) { - _errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString()); - return false; - } - uint32_t imageSize = (uint32_t)firmwareFile.size(); - - if (!sendCommand(port, PROTO_CHIP_VERIFY)) { - return false; - } - - uint8_t fileBuf[READ_MULTI_MAX]; - uint8_t flashBuf[READ_MULTI_MAX]; - uint32_t bytesVerified = 0; - - Q_ASSERT(PROG_MULTI_MAX <= 0x8F); - - while (bytesVerified < imageSize) { - int bytesToRead = imageSize - bytesVerified; - if (bytesToRead > (int)sizeof(fileBuf)) { - bytesToRead = (int)sizeof(fileBuf); - } - - Q_ASSERT((bytesToRead % 4) == 0); - - int bytesRead = firmwareFile.read((char *)fileBuf, bytesToRead); - if (bytesRead == -1 || bytesRead != bytesToRead) { - _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); - return false; - } - - Q_ASSERT(bytesToRead <= 0x8F); - - bool failed = true; - if (write(port, PROTO_READ_MULTI)) { - if (write(port, (uint8_t)bytesToRead)) { - if (write(port, PROTO_EOC)) { - port->flush(); - if (read(port, flashBuf, sizeof(flashBuf))) { - if (getCommandResponse(port)) { - failed = false; - } - } - } - } - } - if (failed) { - _errorString = tr("Verify failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0')); - return false; - } - - for (int i=0; iflush(); - if (read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) { - if (getCommandResponse(port)) { - failed = false; - } - } - } - if (failed) { - return false; - } - - if (_imageCRC != flashCRC) { - _errorString = tr("CRC mismatch: board(0x%1) file(0x%2)").arg(flashCRC, 4, 16, QLatin1Char('0')).arg(_imageCRC, 4, 16, QLatin1Char('0')); - return false; - } - - return true; -} - -bool PX4Bootloader::open(QextSerialPort* port, const QString portName) -{ - Q_ASSERT(!port->isOpen()); - - port->setPortName(portName); - port->setBaudRate(BAUD115200); - port->setDataBits(DATA_8); - port->setParity(PAR_NONE); - port->setStopBits(STOP_1); - port->setFlowControl(FLOW_OFF); - - if (!port->open(QIODevice::ReadWrite | QIODevice::Unbuffered)) { - _errorString = tr("Open failed on port %1: %2").arg(portName).arg(port->errorString()); - return false; - } - - return true; -} - -bool PX4Bootloader::sync(QextSerialPort* port) -{ - // Send sync command - if (sendCommand(port, PROTO_GET_SYNC)) { - return true; - } else { - _errorString.prepend("Sync: "); - return false; - } -} - -bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize) -{ - - if (!getBoardInfo(port, INFO_BL_REV, _bootloaderVersion)) { - goto Error; - } - if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) { - _errorString = tr("Found unsupported bootloader version: %1").arg(_bootloaderVersion); - goto Error; - } - - if (!getBoardInfo(port, INFO_BOARD_ID, _boardID)) { - goto Error; - } - if (_boardID != _boardIDPX4Flow && _boardID != _boardIDPX4FMUV1 && _boardID != _boardIDPX4FMUV2) { - _errorString = tr("Unsupported board: %1").arg(_boardID); - goto Error; - } - - if (!getBoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) { - qWarning() << _errorString; - goto Error; - } - - bootloaderVersion = _bootloaderVersion; - boardID = _boardID; - flashSize = _boardFlashSize; - - return true; - -Error: - _errorString.prepend("Get Board Info: "); - return false; -} - -bool PX4Bootloader::sendBootloaderReboot(QextSerialPort* port) -{ - return write(port, PROTO_BOOT) && write(port, PROTO_EOC); -} diff --git a/src/ui/px4_configuration/PX4Bootloader.h b/src/ui/px4_configuration/PX4Bootloader.h deleted file mode 100644 index 2587c6a..0000000 --- a/src/ui/px4_configuration/PX4Bootloader.h +++ /dev/null @@ -1,165 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Bootloader Utility routines -/// @author Don Gagne - -#ifndef PX4Bootloader_H -#define PX4Bootloader_H - -#include "qextserialport.h" - -#include - -/// @brief This class is used to communicate with the Bootloader. -class PX4Bootloader : public QObject -{ - Q_OBJECT - -public: - explicit PX4Bootloader(QObject *parent = 0); - - /// @brief Returns the error message associated with the last failed call to one of the bootloader - /// utility routine below. - QString errorString(void) { return _errorString; } - - /// @brief Write a byte to the port - /// @param port Port to write to - /// @param data Bytes to write - /// @param maxSize Number of bytes to write - /// @return true: success - bool write(QextSerialPort* port, const uint8_t* data, qint64 maxSize); - bool write(QextSerialPort* port, const uint8_t byte); - - /// @brief Read a set of bytes from the port - /// @param data Read bytes into this buffer - /// @param maxSize Number of bytes to read - /// @param readTimeout Msecs to wait for bytes to become available on port - bool read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout = _readTimout); - - /// @brief Read a PROTO_SYNC command response from the bootloader - /// @param responseTimeout Msecs to wait for response bytes to become available on port - bool getCommandResponse(QextSerialPort* port, const int responseTimeout = _responseTimeout); - - /// @brief Send a PROTO_GET_DEVICE command to retrieve a value from the bootloader - /// @param param Value to retrieve using INFO_BOARD_* enums - /// @param value Returned value - bool getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value); - - /// @brief Send a command to the bootloader - /// @param cmd Command to send using PROTO_* enums - /// @return true: Command sent and valid sync response returned - bool sendCommand(QextSerialPort* port, uint8_t cmd, int responseTimeout = _responseTimeout); - - /// @brief Program the board with the specified firmware - bool program(QextSerialPort* port, const QString& firmwareFilename); - - /// @brief Verify the board flash. How it works depend on bootloader rev - /// Rev 2: Read the flash back and compare it against the firmware file - /// Rev 3: Compare CRCs for flash and file - bool verify(QextSerialPort* port, const QString firmwareFilename); - - /// @brief Read a PROTO_SYNC response from the bootloader - /// @return true: Valid sync response was received - bool sync(QextSerialPort* port); - - /// @brief Retrieve a set of board info from the bootloader - /// @param bootloaderVersion Returned INFO_BL_REV - /// @param boardID Returned INFO_BOARD_ID - /// @param flashSize Returned INFO_FLASH_SIZE - bool getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize); - - /// @brief Opens a port to the bootloader - bool open(QextSerialPort* port, const QString portName); - - /// @brief Sends a PROTO_REBOOT command to the bootloader - bool sendBootloaderReboot(QextSerialPort* port); - - /// @brief Sends a PROTO_ERASE command to the bootlader - bool erase(QextSerialPort* port); - -signals: - /// @brief Signals progress indicator for long running bootloader utility routines - void updateProgramProgress(int curr, int total); - -private: - enum { - // protocol bytes - PROTO_INSYNC = 0x12, ///< 'in sync' byte sent before status - PROTO_EOC = 0x20, ///< end of command - - // Reply bytes - PROTO_OK = 0x10, ///< INSYNC/OK - 'ok' response - PROTO_FAILED = 0x11, ///< INSYNC/FAILED - 'fail' response - PROTO_INVALID = 0x13, ///< INSYNC/INVALID - 'invalid' response for bad commands - - // Command bytes - PROTO_GET_SYNC = 0x21, ///< NOP for re-establishing sync - PROTO_GET_DEVICE = 0x22, ///< get device ID bytes - PROTO_CHIP_ERASE = 0x23, ///< erase program area and reset program address - PROTO_PROG_MULTI = 0x27, ///< write bytes at program address and increment - PROTO_GET_CRC = 0x29, ///< compute & return a CRC - PROTO_BOOT = 0x30, ///< boot the application - - // Command bytes - Rev 2 boootloader only - PROTO_CHIP_VERIFY = 0x24, ///< begin verify mode - PROTO_READ_MULTI = 0x28, ///< read bytes at programm address and increment - - INFO_BL_REV = 1, ///< bootloader protocol revision - BL_REV_MIN = 2, ///< Minimum supported bootlader protocol - BL_REV_MAX = 4, ///< Maximum supported bootloader protocol - INFO_BOARD_ID = 2, ///< board type - INFO_BOARD_REV = 3, ///< board revision - INFO_FLASH_SIZE = 4, ///< max firmware size in bytes - - PROG_MULTI_MAX = 32, ///< write size for PROTO_PROG_MULTI, must be multiple of 4 - READ_MULTI_MAX = 32 ///< read size for PROTO_READ_MULTI, must be multiple of 4 - }; - - bool _findBootloader(void); - bool _downloadFirmware(void); - bool _bootloaderVerifyRev2(QextSerialPort* port, const QString firmwareFilename); - bool _bootloaderVerifyRev3(QextSerialPort* port); - - static const int _boardIDPX4FMUV1 = 5; ///< Board ID for PX4 V1 board - static const int _boardIDPX4FMUV2 = 9; ///< Board ID for PX4 V2 board - static const int _boardIDPX4Flow = 6; ///< Board ID for PX4 Floaw board - - uint32_t _boardID; ///< board id for currently connected board - uint32_t _boardFlashSize; ///< flash size for currently connected board - uint32_t _imageCRC; ///< CRC for image in currently selected firmware file - uint32_t _bootloaderVersion; ///< Bootloader version - - QString _firmwareFilename; ///< Currently selected firmware file to flash - - QString _errorString; ///< Last error - - static const int _eraseTimeout = 20000; ///< Msecs to wait for response from erase command - static const int _rebootTimeout = 10000; ///< Msecs to wait for reboot command to cause serial port to disconnect - static const int _verifyTimeout = 5000; ///< Msecs to wait for response to PROTO_GET_CRC command - static const int _readTimout = 2000; ///< Msecs to wait for read bytes to become avilable - static const int _responseTimeout = 2000; ///< Msecs to wait for command response bytes -}; - -#endif // PX4FirmwareUpgrade_H diff --git a/src/ui/px4_configuration/PX4FirmwareUpgrade.cc b/src/ui/px4_configuration/PX4FirmwareUpgrade.cc deleted file mode 100644 index 7a5726c..0000000 --- a/src/ui/px4_configuration/PX4FirmwareUpgrade.cc +++ /dev/null @@ -1,824 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Firmware Upgrade UI -/// @author Don Gagne - -#include "PX4FirmwareUpgrade.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "QGCFileDialog.h" -#include "QGCMessageBox.h" - -/// @Brief Constructs a new PX4FirmwareUpgrade Widget. This widget is used within the PX4VehicleConfig set of screens. -PX4FirmwareUpgrade::PX4FirmwareUpgrade(QWidget *parent) : - QWidget(parent), - _upgradeState(upgradeStateBegin), - _downloadManager(NULL), - _downloadNetworkReply(NULL) -{ - _ui = new Ui::PX4FirmwareUpgrade; - _ui->setupUi(this); - - _threadController = new PX4FirmwareUpgradeThreadController(this); - Q_CHECK_PTR(_threadController); - - // Connect standard ui elements - connect(_ui->tryAgain, &QPushButton::clicked, this, &PX4FirmwareUpgrade::_tryAgainButton); - connect(_ui->cancel, &QPushButton::clicked, this, &PX4FirmwareUpgrade::_cancelButton); - connect(_ui->next, &QPushButton::clicked, this, &PX4FirmwareUpgrade::_nextButton); - connect(_ui->firmwareCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(_firmwareSelected(int))); - - connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &PX4FirmwareUpgrade::_foundBoard); - connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBootloader, this, &PX4FirmwareUpgrade::_foundBootloader); - connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed, this, &PX4FirmwareUpgrade::_bootloaderSyncFailed); - connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &PX4FirmwareUpgrade::_error); - connect(_threadController, &PX4FirmwareUpgradeThreadController::complete, this, &PX4FirmwareUpgrade::_complete); - connect(_threadController, &PX4FirmwareUpgradeThreadController::findTimeout, this, &PX4FirmwareUpgrade::_findTimeout); - connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &PX4FirmwareUpgrade::_updateProgress); - - connect(&_eraseTimer, &QTimer::timeout, this, &PX4FirmwareUpgrade::_eraseProgressTick); - - _setupState(upgradeStateBegin); -} - -PX4FirmwareUpgrade::~PX4FirmwareUpgrade() -{ -} - -/// @brief Returns the state machine entry for the specified state. -const PX4FirmwareUpgrade::stateMachineEntry* PX4FirmwareUpgrade::_getStateMachineEntry(enum PX4FirmwareUpgrade::upgradeStates state) -{ - static const char* msgBegin = "If you are currently connected to your Pixhawk board via QGroundControl, you must 'Disconnect' from the board. " - "If your board is connected via USB, you must unplug the USB cable.\n\n" - "Click 'Next' when these two steps are complete to begin upgrading."; - static const char* msgBoardSearch = "Plug in your board via USB now..."; - static const char* msgBoardNotFound = "Unable to detect your board. If the board is currently connected via USB. Disconnect it, and click 'Try Again'."; - static const char* msgBootloaderSearch = "Searching for Bootloader..."; - static const char* msgBootloaderNotFound = "Unable to connect to Bootloader. If the board is currently connected via USB. Disconnect it, and click 'Try Again'."; - static const char* msgBootloaderError = "An error occured while communicating with the Bootloader."; - static const char* msgFirmwareSelect = "Please select the firmware you would like to upload to the board from the dropdown to the right."; - static const char* msgFirmwareDownloading = "Firmware downloading..."; - static const char* msgFirmwareDownloadFailed = "Firmware download failed"; - static const char* msgFirmwareBoardErasing = "Erasing old firmware from board..."; - static const char* msgFirmwareBoardEraseFailed = "Board erase failed."; - static const char* msgFirmwareBoardFlashing = "Flashing new firmware onto board..."; - static const char* msgFirmwareBoardFlashError = "A failure has occured while flashing the new firmware to your board. " - "This has left the board in an inconsistent state.\n\n" - "There currently is an known issue which does not yet have a fix which may cause this.\n\n" - "You should click 'Try Again' to attempt the upgrade process again."; - static const char* msgFirmwareBoardVerifying = "Verifying firmware on board..."; - static const char* msgFirmwareBoardVerifyError = "Verification of flash memory on board failed. " - "This has left the board in an inconsistent state. " - "You should click 'Try Again' to attempt the upgrade process again."; - static const char* msgFirmwareBoardUpgraded = "Your board has been upgraded successfully.\n\nYou can now connect to your board via QGroundControl\n\nClick 'Try Again' to do another upgrade."; - - static const stateMachineEntry rgStateMachine[] = { - //State Next command Cancel command Try Again command State Text - { upgradeStateBegin, &PX4FirmwareUpgrade::_findBoard, NULL, NULL, msgBegin }, - { upgradeStateBoardSearch, NULL, &PX4FirmwareUpgrade::_cancelFind, NULL, msgBoardSearch }, - { upgradeStateBoardNotFound, NULL, &PX4FirmwareUpgrade::_cancel, &PX4FirmwareUpgrade::_findBoard, msgBoardNotFound }, - { upgradeStateBootloaderSearch, NULL, &PX4FirmwareUpgrade::_cancelFind, NULL, msgBootloaderSearch }, - { upgradeStateBootloaderNotFound, NULL, &PX4FirmwareUpgrade::_cancel, &PX4FirmwareUpgrade::_restart, msgBootloaderNotFound }, - { upgradeStateBootloaderError, NULL, &PX4FirmwareUpgrade::_cancel, NULL, msgBootloaderError }, - { upgradeStateFirmwareSelect, &PX4FirmwareUpgrade::_getFirmwareFile, &PX4FirmwareUpgrade::_cancel, NULL, msgFirmwareSelect }, - { upgradeStateFirmwareDownloading, NULL, &PX4FirmwareUpgrade::_cancelDownload, NULL, msgFirmwareDownloading }, - { upgradeStateDownloadFailed, NULL, NULL, &PX4FirmwareUpgrade::_restart, msgFirmwareDownloadFailed }, - { upgradeStateErasing, NULL, NULL, NULL, msgFirmwareBoardErasing }, - { upgradeStateEraseError, NULL, &PX4FirmwareUpgrade::_cancel, NULL, msgFirmwareBoardEraseFailed }, - { upgradeStateFlashing, NULL, NULL, NULL, msgFirmwareBoardFlashing }, - { upgradeStateFlashError, NULL, NULL, &PX4FirmwareUpgrade::_restart, msgFirmwareBoardFlashError }, - { upgradeStateVerifying, NULL, NULL, NULL, msgFirmwareBoardVerifying }, - { upgradeStateVerifyError, NULL, NULL, &PX4FirmwareUpgrade::_restart, msgFirmwareBoardVerifyError }, - { upgradeStateBoardUpgraded, NULL, NULL, &PX4FirmwareUpgrade::_restart, msgFirmwareBoardUpgraded }, - }; - - const stateMachineEntry* entry = &rgStateMachine[state]; - - // Validate that our state array has not gotten out of sync - for (size_t i=0; itryAgain->setEnabled(stateMachine->tryAgain != NULL); - _ui->skip->setEnabled(false); - _ui->cancel->setEnabled(stateMachine->cancel != NULL); - _ui->next->setEnabled(stateMachine->next != NULL); - - _ui->statusLog->setText(stateMachine->msg); - - if (_upgradeState == upgradeStateDownloadFailed) { - // Bootloader is still open, reboot to close and heopfully get back to FMU - _threadController->sendBootloaderReboot(); - } - - _updateIndicatorUI(); -} - -/// @brief Updates the Indicator UI which is to the right of the Wizard area to match the current -/// upgrade state. -void PX4FirmwareUpgrade::_updateIndicatorUI(void) -{ - if (_upgradeState == upgradeStateBegin) { - // Reset to intial state. All check boxes unchecked, all additional information hidden. - - _ui->statusLabel->clear(); - - _ui->progressBar->setValue(0); - _ui->progressBar->setTextVisible(false); - - _ui->boardFoundCheck->setCheckState(Qt::Unchecked); - _ui->port->setVisible(false); - _ui->description->setVisible(false); - - _ui->bootloaderFoundCheck->setCheckState(Qt::Unchecked); - _ui->bootloaderVersion->setVisible(false); - _ui->boardID->setVisible(false); - _ui->icon->setVisible(false); - - _ui->firmwareCombo->setVisible(false); - _ui->firmwareCombo->setEnabled(true); - - _ui->selectFirmwareCheck->setCheckState(Qt::Unchecked); - _ui->firmwareDownloadedCheck->setCheckState(Qt::Unchecked); - _ui->boardUpgradedCheck->setCheckState(Qt::Unchecked); - - } else if (_upgradeState == upgradeStateBootloaderSearch){ - // We have found the board - - _ui->statusLabel->clear(); - - _ui->progressBar->setValue(0); - - _ui->boardFoundCheck->setCheckState(Qt::Checked); - - _ui->port->setText("Port: " + _portName); - _ui->description->setText("Name: " +_portDescription); - - _ui->port->setVisible(true); - _ui->description->setVisible(true); - - } else if (_upgradeState == upgradeStateFirmwareSelect) { - // We've found the bootloader and need to set up firmware selection - - _ui->statusLabel->clear(); - - _ui->progressBar->setValue(0); - - _ui->bootloaderFoundCheck->setCheckState(Qt::Checked); - - - _ui->bootloaderVersion->setText(QString("Version: %1").arg(_bootloaderVersion)); - _ui->boardID->setText(QString("Board ID: %1").arg(_boardID)); - _setBoardIcon(_boardID); - _setFirmwareCombo(_boardID); - - _ui->bootloaderVersion->setVisible(true); - _ui->boardID->setVisible(true); - _ui->icon->setVisible(true); - _ui->firmwareCombo->setVisible(true); - _ui->firmwareCombo->setEnabled(true); - _ui->firmwareCombo->setCurrentIndex(0); - - } else if (_upgradeState == upgradeStateFirmwareDownloading) { - - _ui->statusLabel->clear(); - _ui->selectFirmwareCheck->setCheckState(Qt::Checked); - _ui->firmwareCombo->setEnabled(false); - - } else if (_upgradeState == upgradeStateFlashing) { - - _ui->statusLabel->clear(); - _ui->progressBar->setValue(0); - _ui->firmwareDownloadedCheck->setCheckState(Qt::Checked); - - } else if (_upgradeState == upgradeStateBoardUpgraded) { - - _ui->statusLabel->clear(); - _ui->progressBar->setValue(0); - _ui->boardUpgradedCheck->setCheckState((_upgradeState >= upgradeStateBoardUpgraded) ? Qt::Checked : Qt::Unchecked); - - } -} - -/// @brief Responds to a click on the Next Button calling the appropriate method as specified by the state machine. -void PX4FirmwareUpgrade::_nextButton(void) -{ - const stateMachineEntry* stateMachine = _getStateMachineEntry(_upgradeState); - - Q_ASSERT(stateMachine->next != NULL); - - (this->*stateMachine->next)(); -} - - -/// @brief Responds to a click on the Cancel Button calling the appropriate method as specified by the state machine. -void PX4FirmwareUpgrade::_cancelButton(void) -{ - const stateMachineEntry* stateMachine = _getStateMachineEntry(_upgradeState); - - Q_ASSERT(stateMachine->cancel != NULL); - - (this->*stateMachine->cancel)(); -} - -/// @brief Responds to a click on the Try Again Button calling the appropriate method as specified by the state machine. -void PX4FirmwareUpgrade::_tryAgainButton(void) -{ - const stateMachineEntry* stateMachine = _getStateMachineEntry(_upgradeState); - - Q_ASSERT(stateMachine->tryAgain != NULL); - - (this->*stateMachine->tryAgain)(); -} - -/// @brief Cancels a findBoard or findBootloader operation. -void PX4FirmwareUpgrade::_cancelFind(void) -{ - _threadController->cancelFind(); -} - -/// @brief Cancels the current state and returns to the begin start -void PX4FirmwareUpgrade::_cancel(void) -{ - _setupState(upgradeStateBegin); -} - -/// @brief Begins the process or searching for the board -void PX4FirmwareUpgrade::_findBoard(void) -{ - _setupState(upgradeStateBoardSearch); - _threadController->findBoard(_findBoardTimeoutMsec); -} - -/// @brief Called when board has been found by the findBoard process -void PX4FirmwareUpgrade::_foundBoard(bool firstTry, const QString portName, QString portDescription) -{ - if (firstTry) { - // Board is still plugged - QGCMessageBox::critical(tr("Firmware Upgrade"), tr("You must unplug you board before beginning the Firmware Upgrade process.")); - _cancel(); - } else { - _portName = portName; - _portDescription = portDescription; - _setupState(upgradeStateBootloaderSearch); - _findBootloader(); - } -} - -/// @brief Begins the findBootloader process to connect to the bootloader -void PX4FirmwareUpgrade::_findBootloader(void) -{ - _setupState(upgradeStateBootloaderSearch); - _threadController->findBootloader(_portName, _findBootloaderTimeoutMsec); -} - -/// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine -/// to the next step. -void PX4FirmwareUpgrade::_foundBootloader(int bootloaderVersion, int boardID, int flashSize) -{ - _bootloaderVersion = bootloaderVersion; - _boardID = boardID; - _boardFlashSize = flashSize; - _setupState(upgradeStateFirmwareSelect); -} - -/// @brief Called when the findBootloader process is unable to sync to the bootloader. Moves the state -/// machine to the appropriate error state. -void PX4FirmwareUpgrade::_bootloaderSyncFailed(void) -{ - if (_upgradeState == upgradeStateBootloaderSearch) { - // We can connect to the board, but we still can't talk to the bootloader. - qDebug() << "Bootloader sync failed"; - _setupState(upgradeStateBootloaderNotFound); - } else { - Q_ASSERT(false); - } - -} - -/// @brief Called when the findBoard or findBootloader process times out. Moves the state machine to the -/// appropriate error state. -void PX4FirmwareUpgrade::_findTimeout(void) -{ - if (_upgradeState == upgradeStateBoardSearch) { - qDebug() << "Timeout on board search"; - _setupState(upgradeStateBoardNotFound); - } else if (_upgradeState == upgradeStateBootloaderSearch) { - qDebug() << "Timeout on bootloader search"; - _setupState(upgradeStateBoardNotFound); - } else { - Q_ASSERT(false); - } -} - -/// @brief Sets the board image into the icon label according to the board id. -void PX4FirmwareUpgrade::_setBoardIcon(int boardID) -{ - QString imageFile; - - switch (boardID) { - case _boardIDPX4FMUV1: - imageFile = ":/files/images/px4/boards/px4fmu_1.x.png"; - break; - - case _boardIDPX4Flow: - imageFile = ":/files/images/px4/boards/px4flow_1.x.png"; - break; - - case _boardIDPX4FMUV2: - imageFile = ":/files/images/px4/boards/px4fmu_2.x.png"; - break; - } - - if (!imageFile.isEmpty()) { - bool success = _boardIcon.load(imageFile); - Q_ASSERT(success); - Q_UNUSED(success); - - int w = _ui->icon->width(); - int h = _ui->icon->height(); - - _ui->icon->setPixmap(_boardIcon.scaled(w, h, Qt::KeepAspectRatio)); - } -} - -/// @brief Sets up the selections in the firmware combox box associated with the specified -/// board id. -void PX4FirmwareUpgrade::_setFirmwareCombo(int boardID) -{ - _ui->firmwareCombo->clear(); - - static const char* rgPX4FMUV1Firmware[3] = - { - "http://px4.oznet.ch/stable/px4fmu-v1_default.px4", - "http://px4.oznet.ch/beta/px4fmu-v1_default.px4", - "http://px4.oznet.ch/continuous/px4fmu-v1_default.px4" - }; - - static const char* rgPX4FMUV2Firmware[3] = - { - "http://px4.oznet.ch/stable/px4fmu-v2_default.px4", - "http://px4.oznet.ch/beta/px4fmu-v2_default.px4", - "http://px4.oznet.ch/continuous/px4fmu-v2_default.px4" - }; - - static const char* rgPX4FlowFirmware[3] = - { - "http://px4.oznet.ch/stable/px4flow.px4", - "http://px4.oznet.ch/beta/px4flow.px4", - "http://px4.oznet.ch/continuous/px4flow.px4" - }; - - const char** prgFirmware; - switch (boardID) { - case _boardIDPX4FMUV1: - prgFirmware = rgPX4FMUV1Firmware; - break; - - case _boardIDPX4Flow: - prgFirmware = rgPX4FlowFirmware; - break; - - case _boardIDPX4FMUV2: - prgFirmware = rgPX4FMUV2Firmware; - break; - - default: - prgFirmware = NULL; - break; - } - - if (prgFirmware) { - _ui->firmwareCombo->addItem(tr("Standard Version (stable)"), prgFirmware[0]); - _ui->firmwareCombo->addItem(tr("Beta Testing (beta)"), prgFirmware[1]); - _ui->firmwareCombo->addItem(tr("Developer Build (master)"), prgFirmware[2]); - } - _ui->firmwareCombo->addItem(tr("Custom firmware file..."), "selectfile"); -} - -/// @brief Called when the selection in the firmware combo box changes. Updates the wizard -/// text appropriately with licensing and possibly warning information. -void PX4FirmwareUpgrade::_firmwareSelected(int index) -{ -#define SELECT_FIRMWARE_LICENSE "By clicking Next you agree to the terms and disclaimer of the BSD open source license, as redistributed with the source code." - - if (_upgradeState == upgradeStateFirmwareSelect) { - switch (index) { - case 0: - case 3: - _ui->statusLog->setText(tr(SELECT_FIRMWARE_LICENSE)); - break; - - case 1: - _ui->statusLog->setText(tr("WARNING: BETA FIRMWARE\n" - "This firmware version is ONLY intended for beta testers. " - "Although it has received FLIGHT TESTING, it represents actively changed code. Do NOT use for normal operation.\n\n" - SELECT_FIRMWARE_LICENSE)); - break; - - case 2: - _ui->statusLog->setText(tr("WARNING: CONTINUOUS BUILD FIRMWARE\n" - "This firmware has NOT BEEN FLIGHT TESTED. " - "It is only intended for DEVELOPERS. Run bench tests without props first. " - "Do NOT fly this without addional safety precautions. Follow the mailing " - "list actively when using it.\n\n" - SELECT_FIRMWARE_LICENSE)); - break; - } - _ui->next->setEnabled(!_ui->firmwareCombo->itemData(index).toString().isEmpty()); - } -} - -/// @brief Prompts the user to select a firmware file if needed and moves the state machine to the -/// download firmware state. -void PX4FirmwareUpgrade::_getFirmwareFile(void) -{ - int index = _ui->firmwareCombo->currentIndex(); - _firmwareFilename = _ui->firmwareCombo->itemData(index).toString(); - Q_ASSERT(!_firmwareFilename.isEmpty()); - if (_firmwareFilename == "selectfile") { - _firmwareFilename = QGCFileDialog::getOpenFileName( - this, - tr("Load Firmware File"), // Dialog Caption - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), // Initial directory - tr("Firmware Files (*.px4 *.bin)")); // File filter - } - if (!_firmwareFilename.isEmpty()) { - _downloadFirmware(); - } -} - -/// @brief Begins the process of downloading the selected firmware file. -void PX4FirmwareUpgrade::_downloadFirmware(void) -{ - // Split out filename from path - Q_ASSERT(!_firmwareFilename.isEmpty()); - QString firmwareFilename = QFileInfo(_firmwareFilename).fileName(); - Q_ASSERT(!firmwareFilename.isEmpty()); - - // Determine location to download file to - QString downloadFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); - if (downloadFile.isEmpty()) { - downloadFile = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - if (downloadFile.isEmpty()) { - _setupState(upgradeStateDownloadFailed); - _ui->statusLabel->setText(tr("Unabled to find writable download location. Tried downloads and temp directory.")); - return; - } - } - Q_ASSERT(!downloadFile.isEmpty()); - downloadFile += "/" + firmwareFilename; - - QUrl firmwareUrl; - if (_firmwareFilename.startsWith("http:")) { - firmwareUrl.setUrl(_firmwareFilename); - } else { - firmwareUrl = QUrl::fromLocalFile(_firmwareFilename); - } - Q_ASSERT(firmwareUrl.isValid()); - - QNetworkRequest networkRequest(firmwareUrl); - - // Store download file location in user attribute so we can retrieve when the download finishes - networkRequest.setAttribute(QNetworkRequest::User, downloadFile); - - _downloadManager = new QNetworkAccessManager(this); - Q_CHECK_PTR(_downloadManager); - _downloadNetworkReply = _downloadManager->get(networkRequest); - Q_ASSERT(_downloadNetworkReply); - connect(_downloadNetworkReply, &QNetworkReply::downloadProgress, this, &PX4FirmwareUpgrade::_downloadProgress); - connect(_downloadNetworkReply, &QNetworkReply::finished, this, &PX4FirmwareUpgrade::_downloadFinished); - // FIXME - //connect(_downloadNetworkReply, &QNetworkReply::error, this, &PX4FirmwareUpgrade::_downloadError); - connect(_downloadNetworkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(_downloadError(QNetworkReply::NetworkError))); - - _setupState(upgradeStateFirmwareDownloading); -} - -/// @brief Cancels a download which is in progress. -void PX4FirmwareUpgrade::_cancelDownload(void) -{ - _downloadNetworkReply->abort(); -} - -/// @brief Updates the progress indicator while downloading -void PX4FirmwareUpgrade::_downloadProgress(qint64 curr, qint64 total) -{ - // Take care of cases where 0 / 0 is emitted as error return value - if (total > 0) { - _ui->progressBar->setValue((curr*100) / total); - } -} - -/// @brief Called when the firmware download completes. -void PX4FirmwareUpgrade::_downloadFinished(void) -{ - QNetworkReply* reply = qobject_cast(QObject::sender()); - Q_ASSERT(reply); - - Q_ASSERT(_downloadNetworkReply == reply); - - _downloadManager->deleteLater(); - _downloadManager = NULL; - - // When an error occurs or the user cancels the download, we still end up here. So bail out in - // those cases. - if (reply->error() != QNetworkReply::NoError) { - return; - } - - // Download file location is in user attribute - QString downloadFilename = reply->request().attribute(QNetworkRequest::User).toString(); - Q_ASSERT(!downloadFilename.isEmpty()); - - // Store downloaded file in download location - QFile file(downloadFilename); - if (!file.open(QIODevice::WriteOnly)) { - _ui->statusLabel->setText(tr("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString())); - _setupState(upgradeStateDownloadFailed); - return; - } - - file.write(reply->readAll()); - file.close(); - - - if (downloadFilename.endsWith(".px4")) { - // We need to collect information from the .px4 file as well as pull the binary image out to a seperate file. - - QFile px4File(downloadFilename); - if (!px4File.open(QIODevice::ReadOnly | QIODevice::Text)) { - _ui->statusLabel->setText(tr("Unable to open firmware file %1, error: %2").arg(downloadFilename).arg(px4File.errorString())); - _setupState(upgradeStateDownloadFailed); - return; - } - - QByteArray bytes = px4File.readAll(); - px4File.close(); - QJsonDocument doc = QJsonDocument::fromJson(bytes); - - if (doc.isNull()) { - _ui->statusLabel->setText(tr("supplied file is not a valid JSON document")); - _setupState(upgradeStateDownloadFailed); - return; - } - - QJsonObject px4Json = doc.object(); - - // Make sure the keys we need are available - static const char* rgJsonKeys[] = { "board_id", "image_size", "description", "git_identity" }; - for (size_t i=0; istatusLabel->setText(tr("Incorrectly formatted firmware file. No %1 key.").arg(rgJsonKeys[i])); - _setupState(upgradeStateDownloadFailed); - return; - } - } - - uint32_t firmwareBoardID = (uint32_t)px4Json.value(QString("board_id")).toInt(); - if (firmwareBoardID != _boardID) { - _ui->statusLabel->setText(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardID).arg(_boardID)); - _setupState(upgradeStateDownloadFailed); - return; - } - - _imageSize = px4Json.value(QString("image_size")).toInt(); - if (_imageSize == 0) { - _ui->statusLabel->setText(tr("Image size of 0 in .px4 file %1").arg(downloadFilename)); - _setupState(upgradeStateDownloadFailed); - return; - } - qDebug() << "Image size from px4:" << _imageSize; - - // Convert image from base-64 and decompress - - // XXX Qt's JSON string handling is terribly broken, strings - // with some length (18K / 25K) are just weirdly cut. - // The code below works around this by manually 'parsing' - // for the image string. Since its compressed / checksummed - // this should be fine. - - QStringList list = QString(bytes).split("\"image\": \""); - list = list.last().split("\""); - - // Convert String to QByteArray and unzip it - QByteArray raw; - - // Store image size - raw.append((unsigned char)((_imageSize >> 24) & 0xFF)); - raw.append((unsigned char)((_imageSize >> 16) & 0xFF)); - raw.append((unsigned char)((_imageSize >> 8) & 0xFF)); - raw.append((unsigned char)((_imageSize >> 0) & 0xFF)); - - QByteArray raw64 = list.first().toUtf8(); - - raw.append(QByteArray::fromBase64(raw64)); - QByteArray uncompressed = qUncompress(raw); - - QByteArray b = uncompressed; - - if (b.count() == 0) { - _ui->statusLabel->setText(tr("Firmware file has 0 length image")); - _setupState(upgradeStateDownloadFailed); - return; - } - if (b.count() != (int)_imageSize) { - _ui->statusLabel->setText(tr("Image size for decompressed image does not match stored image size: Expected(%1) Actual(%2)").arg(_imageSize).arg(b.count())); - _setupState(upgradeStateDownloadFailed); - return; - } - - // Pad image to 4-byte boundary - while ((b.count() % 4) != 0) { - b.append(static_cast(static_cast(0xFF))); - } - - // Store decompressed image file in same location as original download file - QDir downloadDir = QFileInfo(downloadFilename).dir(); - QString decompressFilename = downloadDir.filePath("PX4FlashUpgrade.bin"); - - QFile decompressFile(decompressFilename); - if (!decompressFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - _ui->statusLabel->setText(tr("Unable to open decompressed file %1 for writing, error: %2").arg(decompressFilename).arg(decompressFile.errorString())); - _setupState(upgradeStateDownloadFailed); - return; - } - - qint64 bytesWritten = decompressFile.write(b); - if (bytesWritten != b.count()) { - _ui->statusLabel->setText(tr("Write failed for decompressed image file, error: %1").arg(decompressFile.errorString())); - _setupState(upgradeStateDownloadFailed); - return; - } - decompressFile.close(); - - _firmwareFilename = decompressFilename; - } else if (downloadFilename.endsWith(".bin")) { - uint32_t firmwareBoardID = 0; - - // Take some educated guesses on board id based on firmware build system file name conventions - - if (downloadFilename.toLower().contains("px4fmu-v1")) { - firmwareBoardID = _boardIDPX4FMUV2; - } else if (downloadFilename.toLower().contains("px4flow")) { - firmwareBoardID = _boardIDPX4Flow; - } else if (downloadFilename.toLower().contains("px4fmu-v1")) { - firmwareBoardID = _boardIDPX4FMUV1; - } - - if (firmwareBoardID != 0 && firmwareBoardID != _boardID) { - _ui->statusLabel->setText(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardID).arg(_boardID)); - _setupState(upgradeStateDownloadFailed); - return; - } - - _firmwareFilename = downloadFilename; - - QFile binFile(_firmwareFilename); - if (!binFile.open(QIODevice::ReadOnly)) { - _ui->statusLabel->setText(tr("Unabled to open firmware file %1, %2").arg(_firmwareFilename).arg(binFile.errorString())); - _setupState(upgradeStateDownloadFailed); - } - _imageSize = (uint32_t)binFile.size(); - binFile.close(); - } else { - // Standard firmware builds (stable/continuous/...) are always .bin or .px4. Select file dialog for custom - // firmware filters to .bin and .px4. So we should never get a file that ends in anything else. - Q_ASSERT(false); - } - - if (_imageSize > _boardFlashSize) { - _ui->statusLabel->setText(tr("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_boardFlashSize)); - _setupState(upgradeStateDownloadFailed); - return; - } - - _erase(); -} - -/// @brief Called when an error occurs during download -void PX4FirmwareUpgrade::_downloadError(QNetworkReply::NetworkError code) -{ - if (code == QNetworkReply::OperationCanceledError) { - _ui->statusLabel->setText(tr("Download cancelled")); - } else { - _ui->statusLabel->setText(tr("Error during download. Error: %1").arg(code)); - } - - _setupState(upgradeStateDownloadFailed); -} - -/// @brief Erase the board -void PX4FirmwareUpgrade::_erase(void) -{ - // We set up our own progress bar for erase since the erase command does not provide one - _eraseTickCount = 0; - _eraseTimer.start(_eraseTickMsec); - _setupState(upgradeStateErasing); - - // Erase command - _threadController->erase(); -} - -/// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the -/// appropriate next step. -void PX4FirmwareUpgrade::_complete(const int command) -{ - if (command == PX4FirmwareUpgradeThreadWorker::commandProgram) { - _setupState(upgradeStateVerifying); - _threadController->verify(_firmwareFilename); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandVerify) { - _setupState(upgradeStateBoardUpgraded); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandErase) { - _eraseTimer.stop(); - _setupState(upgradeStateFlashing); - _threadController->program(_firmwareFilename); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandCancel) { - if (_upgradeState == upgradeStateBoardSearch) { - _setupState(upgradeStateBoardNotFound); - } else if (_upgradeState == upgradeStateBootloaderSearch) { - _setupState(upgradeStateBootloaderNotFound); - } else { - Q_ASSERT(false); - } - } else { - Q_ASSERT(false); - } -} - -/// @brief Signals that an error has occured with the specified bootloader commands. Moves the state machine -/// to the appropriate error state. -void PX4FirmwareUpgrade::_error(const int command, const QString errorString) -{ - _ui->statusLabel->setText(tr("Error: %1").arg(errorString)); - - if (command == PX4FirmwareUpgradeThreadWorker::commandProgram) { - _setupState(upgradeStateFlashError); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandErase) { - _setupState(upgradeStateEraseError); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandBootloader) { - _setupState(upgradeStateBootloaderError); - } else if (command == PX4FirmwareUpgradeThreadWorker::commandVerify) { - _setupState(upgradeStateVerifyError); - } else { - Q_ASSERT(false); - } -} - -/// @brief Updates the progress bar from long running bootloader commands -void PX4FirmwareUpgrade::_updateProgress(int curr, int total) -{ - _ui->progressBar->setValue((curr*100) / total); -} - -/// @brief Resets the state machine back to the beginning -void PX4FirmwareUpgrade::_restart(void) -{ - _setupState(upgradeStateBegin); -} - -/// @brief Moves the progress bar ahead on tick while erasing the board -void PX4FirmwareUpgrade::_eraseProgressTick(void) -{ - _eraseTickCount++; - _ui->progressBar->setValue((_eraseTickCount*_eraseTickMsec*100) / _eraseTotalMsec); -} diff --git a/src/ui/px4_configuration/PX4FirmwareUpgrade.h b/src/ui/px4_configuration/PX4FirmwareUpgrade.h deleted file mode 100644 index 6ab2f82..0000000 --- a/src/ui/px4_configuration/PX4FirmwareUpgrade.h +++ /dev/null @@ -1,160 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Firmware Upgrade UI -/// @author Don Gagne - -#ifndef PX4FirmwareUpgrade_H -#define PX4FirmwareUpgrade_H - -#include -#include -#include -#include -#include - -#include "qextserialport.h" - -#include - -#include "PX4FirmwareUpgradeThread.h" - -#include "ui_PX4FirmwareUpgrade.h" - -namespace Ui { - class PX4RCCalibration; -} - -class PX4FirmwareUpgrade : public QWidget -{ - Q_OBJECT - -public: - explicit PX4FirmwareUpgrade(QWidget *parent = 0); - ~PX4FirmwareUpgrade(); - -private slots: - void _tryAgainButton(void); - void _cancelButton(void); - void _nextButton(void); - void _firmwareSelected(int index); - void _downloadProgress(qint64 curr, qint64 total); - void _downloadFinished(void); - void _downloadError(QNetworkReply::NetworkError code); - void _foundBoard(bool firstTry, const QString portname, QString portDescription); - void _foundBootloader(int bootloaderVersion, int boardID, int flashSize); - void _error(const int command, const QString errorString); - void _bootloaderSyncFailed(void); - void _findTimeout(void); - void _complete(const int command); - void _updateProgress(int curr, int total); - void _restart(void); - void _eraseProgressTick(void); - -private: - /// @brief The various states that the upgrade process progresses through. - enum upgradeStates { - upgradeStateBegin, - upgradeStateBoardSearch, - upgradeStateBoardNotFound, - upgradeStateBootloaderSearch, - upgradeStateBootloaderNotFound, - upgradeStateBootloaderError, - upgradeStateFirmwareSelect, - upgradeStateFirmwareDownloading, - upgradeStateDownloadFailed, - upgradeStateErasing, - upgradeStateEraseError, - upgradeStateFlashing, - upgradeStateFlashError, - upgradeStateVerifying, - upgradeStateVerifyError, - upgradeStateBoardUpgraded, - upgradeStateMax - }; - - void _setupState(enum upgradeStates state); - void _updateIndicatorUI(void); - - void _findBoard(void); - void _findBootloader(void); - void _cancel(void); - void _cancelFind(void); - void _getFirmwareFile(void); - - void _setBoardIcon(int boardID); - void _setFirmwareCombo(int boardID); - - void _downloadFirmware(void); - void _cancelDownload(void); - - void _erase(void); - - typedef void (PX4FirmwareUpgrade::*stateFunc)(void); - struct stateMachineEntry { - enum upgradeStates state; ///< State machine state, used to verify correctness of entry - stateFunc next; ///< Method to call when Next is clicked, NULL for Next not available - stateFunc cancel; ///< Method to call when Cancel is clicked, NULL for Cancel not available - stateFunc tryAgain; ///< Method to call when Try Again is clicked, NULL for Try Again not available - const char* msg; ///< Text message to display to user for this state - }; - - const struct stateMachineEntry* _getStateMachineEntry(enum upgradeStates state); - - enum upgradeStates _upgradeState; ///< Current state of the upgrade state machines - - QString _portName; - QString _portDescription; - uint32_t _bootloaderVersion; - - static const int _boardIDPX4FMUV1 = 5; ///< Board ID for PX4 V1 board - static const int _boardIDPX4FMUV2 = 9; ///< Board ID for PX4 V2 board - static const int _boardIDPX4Flow = 6; ///< Board ID for PX4 Flow board - - uint32_t _boardID; ///< Board ID - uint32_t _boardFlashSize; ///< Flash size in bytes of board - uint32_t _imageSize; ///< Image size of firmware being flashed - - QPixmap _boardIcon; ///< Icon used to display image of board - - QString _firmwareFilename; ///< Image which we are going to flash to the board - - QNetworkAccessManager* _downloadManager; ///< Used for firmware file downloading across the internet - QNetworkReply* _downloadNetworkReply; ///< Used for firmware file downloading across the internet - - /// @brief Thread controller which is used to run bootloader commands on seperate thread - PX4FirmwareUpgradeThreadController* _threadController; - - static const int _eraseTickMsec = 500; ///< Progress bar update tick time for erase - static const int _eraseTotalMsec = 15000; ///< Estimated amount of time erase takes - int _eraseTickCount; ///< Number of ticks for erase progress update - QTimer _eraseTimer; ///< Timer used to update progress bar for erase - - static const int _findBoardTimeoutMsec = 30000; ///< Amount of time for user to plug in USB - static const int _findBootloaderTimeoutMsec = 5000; ///< Amount time to look for bootloader - - Ui::PX4FirmwareUpgrade* _ui; -}; - -#endif // PX4FirmwareUpgrade_H diff --git a/src/ui/px4_configuration/PX4FirmwareUpgrade.ui b/src/ui/px4_configuration/PX4FirmwareUpgrade.ui deleted file mode 100644 index 048eceb..0000000 --- a/src/ui/px4_configuration/PX4FirmwareUpgrade.ui +++ /dev/null @@ -1,263 +0,0 @@ - - - PX4FirmwareUpgrade - - - Qt::ApplicationModal - - - - 0 - 0 - 727 - 527 - - - - - 0 - 0 - - - - - 727 - 527 - - - - Form - - - 1.000000000000000 - - - - - 0 - 0 - 726 - 525 - - - - - - - - - Board found - - - - - - - Port - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Description - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Bootloader found - - - - - - - Bootloader Version - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Board ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 200 - 100 - - - - Icon - - - - - - - Select Firmware - - - - - - - - - - Firmware downloaded - - - - - - - Board upgraded - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - 0 - - - - - 400 - 180 - - - - Status log - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - 0 - 0 - - - - - 400 - 16 - - - - TextLabel - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - - Try Again - - - - - - - Skip - - - - - - - Cancel - - - - - - - Next - - - - - - - - - 24 - - - - - - - Qt::Vertical - - - - 20 - 60 - - - - - - - - - - - - diff --git a/src/ui/px4_configuration/PX4FirmwareUpgradeThread.cc b/src/ui/px4_configuration/PX4FirmwareUpgradeThread.cc deleted file mode 100644 index 95b4e90..0000000 --- a/src/ui/px4_configuration/PX4FirmwareUpgradeThread.cc +++ /dev/null @@ -1,292 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Firmware Upgrade operations which occur on a seperate thread. -/// @author Don Gagne - -#include "PX4FirmwareUpgradeThread.h" -#include "PX4Bootloader.h" - -#include -#include -#include - -PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(QObject* parent) : - QObject(parent), - _bootloader(NULL), - _bootloaderPort(NULL), - _timerTimeout(NULL), - _timerRetry(NULL) -{ - -} - -PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker() -{ - if (_bootloaderPort) { - // deleteLater so delete happens on correct thread - _bootloaderPort->deleteLater(); - } -} - -/// @brief Initializes the PX4FirmwareUpgradeThreadWorker with the various child objects which must be created -/// on the worker thread. -void PX4FirmwareUpgradeThreadWorker::init(void) -{ - // We create the timers here so that they are on the right thread - - Q_ASSERT(_timerTimeout == NULL); - _timerTimeout = new QTimer(this); - Q_CHECK_PTR(_timerTimeout); - connect(_timerTimeout, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::timeout); - _timerTimeout->setSingleShot(true); - - Q_ASSERT(_timerRetry == NULL); - _timerRetry = new QTimer(this); - Q_CHECK_PTR(_timerRetry); - _timerRetry->setSingleShot(true); - _timerRetry->setInterval(_retryTimeout); - - Q_ASSERT(_bootloader == NULL); - _bootloader = new PX4Bootloader(this); - connect(_bootloader, &PX4Bootloader::updateProgramProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgramProgress); -} - -void PX4FirmwareUpgradeThreadWorker::findBoard(int msecTimeout) -{ - _findBoardFirstAttempt = true; - connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce); - _timerTimeout->start(msecTimeout); - _elapsed.start(); - _findBoardOnce(); -} - -void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void) -{ - qDebug() << "_findBoardOnce"; - - QString portName; - QString portDescription; - - foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) { - if (!info.portName().isEmpty() && (info.description().contains("PX4") || info.vendorIdentifier() == 9900 /* 3DR */)) { - - qDebug() << "Found Board:"; - qDebug() << "\tport name:" << info.portName(); - qDebug() << "\tdescription:" << info.description(); - qDebug() << "\tsystem location:" << info.systemLocation(); - qDebug() << "\tvendor ID:" << info.vendorIdentifier(); - qDebug() << "\tproduct ID:" << info.productIdentifier(); - - portName = info.systemLocation(); - portDescription = info.description(); - - _closeFind(); - emit foundBoard(_findBoardFirstAttempt, portName, portDescription); - return; - } - } - - _findBoardFirstAttempt = false; - - emit updateProgress(_elapsed.elapsed(), _timerTimeout->interval()); - _timerRetry->start(); -} - -void PX4FirmwareUpgradeThreadWorker::findBootloader(const QString portName, int msecTimeout) -{ - Q_UNUSED(msecTimeout); - - // Once the port shows up, we only try to connect to the bootloader a single time - _portName = portName; - _findBootloaderOnce(); -} - -void PX4FirmwareUpgradeThreadWorker::_findBootloaderOnce(void) -{ - qDebug() << "_findBootloaderOnce"; - - uint32_t bootloaderVersion, boardID, flashSize; - - _bootloaderPort = new QextSerialPort(QextSerialPort::Polling); - Q_CHECK_PTR(_bootloaderPort); - - if (_bootloader->open(_bootloaderPort, _portName)) { - if (_bootloader->sync(_bootloaderPort)) { - if (_bootloader->getBoardInfo(_bootloaderPort, bootloaderVersion, boardID, flashSize)) { - _closeFind(); - qDebug() << "Found bootloader"; - emit foundBootloader(bootloaderVersion, boardID, flashSize); - return; - } - } - } - - _closeFind(); - _bootloaderPort->close(); - _bootloaderPort->deleteLater(); - _bootloaderPort = NULL; - qDebug() << "Bootloader error:" << _bootloader->errorString(); - emit error(commandBootloader, _bootloader->errorString()); -} - -void PX4FirmwareUpgradeThreadWorker::_closeFind(void) -{ - emit updateProgress(100, 100); - disconnect(_timerRetry, SIGNAL(timeout()), 0, 0); - _timerRetry->stop(); - _timerTimeout->stop(); -} - -void PX4FirmwareUpgradeThreadWorker::cancelFind(void) -{ - _closeFind(); - emit complete(commandCancel); -} - -void PX4FirmwareUpgradeThreadWorker::timeout(void) -{ - qDebug() << "Find timeout"; - _closeFind(); - emit findTimeout(); -} - -void PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot(void) -{ - _bootloader->sendBootloaderReboot(_bootloaderPort); - _bootloaderPort->deleteLater(); - _bootloaderPort = NULL; -} - -void PX4FirmwareUpgradeThreadWorker::program(const QString firmwareFilename) -{ - qDebug() << "Program"; - if (!_bootloader->program(_bootloaderPort, firmwareFilename)) { - _bootloaderPort->deleteLater(); - _bootloaderPort = NULL; - qDebug() << "Program failed:" << _bootloader->errorString(); - emit error(commandProgram, _bootloader->errorString()); - } else { - qDebug() << "Program complete"; - emit complete(commandProgram); - } -} - -void PX4FirmwareUpgradeThreadWorker::verify(const QString firmwareFilename) -{ - qDebug() << "Verify"; - if (!_bootloader->verify(_bootloaderPort, firmwareFilename)) { - qDebug() << "Verify failed:" << _bootloader->errorString(); - emit error(commandVerify, _bootloader->errorString()); - } else { - qDebug() << "Verify complete"; - emit complete(commandVerify); - } - _bootloaderPort->deleteLater(); - _bootloaderPort = NULL; -} - -void PX4FirmwareUpgradeThreadWorker::erase(void) -{ - qDebug() << "Erase"; - if (!_bootloader->erase(_bootloaderPort)) { - _bootloaderPort->deleteLater(); - _bootloaderPort = NULL; - qDebug() << "Erase failed:" << _bootloader->errorString(); - emit error(commandErase, _bootloader->errorString()); - } else { - qDebug() << "Erase complete"; - emit complete(commandErase); - } -} - -PX4FirmwareUpgradeThreadController::PX4FirmwareUpgradeThreadController(QObject* parent) : - QObject(parent) -{ - _worker = new PX4FirmwareUpgradeThreadWorker(); - Q_CHECK_PTR(_worker); - - _workerThread = new QThread(this); - Q_CHECK_PTR(_workerThread); - _worker->moveToThread(_workerThread); - - connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoard, this, &PX4FirmwareUpgradeThreadController::_foundBoard); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBootloader, this, &PX4FirmwareUpgradeThreadController::_foundBootloader); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::bootloaderSyncFailed, this, &PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::complete, this, &PX4FirmwareUpgradeThreadController::_complete); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::findTimeout, this, &PX4FirmwareUpgradeThreadController::_findTimeout); - connect(_worker, &PX4FirmwareUpgradeThreadWorker::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress); - - connect(this, &PX4FirmwareUpgradeThreadController::_initThreadWorker, _worker, &PX4FirmwareUpgradeThreadWorker::init); - connect(this, &PX4FirmwareUpgradeThreadController::_findBoardOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::findBoard); - connect(this, &PX4FirmwareUpgradeThreadController::_findBootloaderOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::findBootloader); - connect(this, &PX4FirmwareUpgradeThreadController::_sendBootloaderRebootOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot); - connect(this, &PX4FirmwareUpgradeThreadController::_programOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::program); - connect(this, &PX4FirmwareUpgradeThreadController::_verifyOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::verify); - connect(this, &PX4FirmwareUpgradeThreadController::_eraseOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::erase); - connect(this, &PX4FirmwareUpgradeThreadController::_cancelFindOnThread, _worker, &PX4FirmwareUpgradeThreadWorker::cancelFind); - - _workerThread->start(); - - emit _initThreadWorker(); -} - -PX4FirmwareUpgradeThreadController::~PX4FirmwareUpgradeThreadController() -{ - _workerThread->quit(); - _workerThread->wait(); -} - -void PX4FirmwareUpgradeThreadController::findBoard(int msecTimeout) -{ - qDebug() << "PX4FirmwareUpgradeThreadController::findBoard"; - emit _findBoardOnThread(msecTimeout); -} - -void PX4FirmwareUpgradeThreadController::findBootloader(const QString& portName, int msecTimeout) -{ - qDebug() << "PX4FirmwareUpgradeThreadController::findBootloader"; - emit _findBootloaderOnThread(portName, msecTimeout); -} - -void PX4FirmwareUpgradeThreadController::_foundBoard(bool firstTry, const QString portName, QString portDescription) -{ - emit foundBoard(firstTry, portName, portDescription); -} - -void PX4FirmwareUpgradeThreadController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize) -{ - emit foundBootloader(bootloaderVersion, boardID, flashSize); -} - -void PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed(void) -{ - emit bootloaderSyncFailed(); -} - -void PX4FirmwareUpgradeThreadController::_findTimeout(void) -{ - emit findTimeout(); -} diff --git a/src/ui/px4_configuration/PX4FirmwareUpgradeThread.h b/src/ui/px4_configuration/PX4FirmwareUpgradeThread.h deleted file mode 100644 index 141c130..0000000 --- a/src/ui/px4_configuration/PX4FirmwareUpgradeThread.h +++ /dev/null @@ -1,183 +0,0 @@ -/*===================================================================== - - 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 . - - ======================================================================*/ - -/// @file -/// @brief PX4 Firmware Upgrade operations which occur on a seperate thread. -/// @author Don Gagne - -#ifndef PX4FirmwareUpgradeThread_H -#define PX4FirmwareUpgradeThread_H - -#include -#include -#include -#include - -#include "qextserialport.h" - -#include - -#include "PX4Bootloader.h" - -/// @brief Used to run bootloader commands on a seperate thread. These routines are mainly meant to to be called -/// internally by the PX4FirmwareUpgradeThreadController. Clients should call the various public methods -/// exposed by PX4FirmwareUpgradeThreadController. -class PX4FirmwareUpgradeThreadWorker : public QObject -{ - Q_OBJECT - -public: - PX4FirmwareUpgradeThreadWorker(QObject* parent = NULL); - ~PX4FirmwareUpgradeThreadWorker(); - - enum { - commandBootloader, - commandProgram, - commandVerify, - commandErase, - commandCancel - }; - -public slots: - void init(void); - void findBoard(int msecTimeout); - void findBootloader(const QString portName, int msecTimeout); - void timeout(void); - void cancelFind(void); - void sendBootloaderReboot(void); - void program(const QString firmwareFilename); - void verify(const QString firmwareFilename); - void erase(void); - -signals: - void foundBoard(bool firstTry, const QString portname, QString portDescription); - void foundBootloader(int bootloaderVersion, int boardID, int flashSize); - void bootloaderSyncFailed(void); - void error(const int command, const QString errorString); - void complete(const int command); - void findTimeout(void); - void updateProgress(int curr, int total); - -private slots: - void _findBoardOnce(void); - void _findBootloaderOnce(void); - void _updateProgramProgress(int curr, int total) { emit updateProgress(curr, total); } - void _closeFind(void); - -private: - PX4Bootloader* _bootloader; - QextSerialPort* _bootloaderPort; - QTimer* _timerTimeout; - QTimer* _timerRetry; - QTime _elapsed; - QString _portName; - static const int _retryTimeout = 1000; - bool _findBoardFirstAttempt; -}; - -/// @brief Provides methods to interact with the bootloader. The commands themselves are signalled -/// across to PX4FirmwareUpgradeThreadWorker so that they run on the seperate thread. -class PX4FirmwareUpgradeThreadController : public QObject -{ - Q_OBJECT - -public: - PX4FirmwareUpgradeThreadController(QObject* parent = NULL); - ~PX4FirmwareUpgradeThreadController(void); - - /// @brief Begins the process of searching for a PX4 board connected to any serial port. - /// @param msecTimeout Numbers of msecs to continue looking for a board to become available. - void findBoard(int msecTimeout); - - /// @brief Begins the process of attempting to communicate with the bootloader on the specified port. - /// @param portName Name of port to attempt a bootloader connection on. - /// @param msecTimeout Number of msecs to continue to wait for a bootloader to appear on the port. - void findBootloader(const QString& portName, int msecTimeout); - - /// @brief Cancel an in progress findBoard or FindBootloader - void cancelFind(void) { emit _cancelFindOnThread(); } - - /// @brief Sends a reboot command to the bootloader - void sendBootloaderReboot(void) { emit _sendBootloaderRebootOnThread(); } - - /// @brief Flash the specified firmware onto the board - void program(const QString firmwareFilename) { emit _programOnThread(firmwareFilename); } - - /// @brief Verify the board flash with respect to the specified firmware image - void verify(const QString firmwareFilename) { emit _verifyOnThread(firmwareFilename); } - - /// @brief Send and erase command to the bootloader - void erase(void) { emit _eraseOnThread(); } - -signals: - /// @brief Emitted by the findBoard process when it finds the board. - /// @param firstTry true: board found on first attempt - /// @param portName Port that board is on - /// @param portDescription User friendly port description - void foundBoard(bool firstTry, const QString portname, QString portDescription); - - /// @brief Emitted by the findBootloader process when has a connection to the bootloader - void foundBootloader(int bootloaderVersion, int boardID, int flashSize); - - /// @brief Emitted by the bootloader commands when an error occurs. - /// @param errorCommand Command which caused the error, using PX4FirmwareUpgradeThreadWorker command* enum values - void error(const int errorCommand, const QString errorString); - - /// @brief Signalled when the findBootloader process connects to the port, but cannot sync to the - /// bootloader. - void bootloaderSyncFailed(void); - - /// @brief Signalled when the findBoard or findBootloader process times out before success - void findTimeout(void); - - /// @brief Signalled by the bootloader commands other than find* that they have complete successfully. - /// @param command Command which completed, using PX4FirmwareUpgradeThreadWorker command* enum values - void complete(const int command); - - /// @brief Signalled to update progress for long running bootloader commands - void updateProgress(int curr, int total); - - void _initThreadWorker(void); - void _findBoardOnThread(int msecTimeout); - void _findBootloaderOnThread(const QString& portName, int msecTimeout); - void _sendBootloaderRebootOnThread(void); - void _programOnThread(const QString firmwareFilename); - void _verifyOnThread(const QString firmwareFilename); - void _eraseOnThread(void); - void _cancelFindOnThread(void); - -private slots: - void _foundBoard(bool firstTry, const QString portname, QString portDescription); - void _foundBootloader(int bootloaderVersion, int boardID, int flashSize); - void _bootloaderSyncFailed(void); - void _error(const int errorCommand, const QString errorString) { emit error(errorCommand, errorString); } - void _complete(const int command) { emit complete(command); } - void _findTimeout(void); - void _updateProgress(int curr, int total) { emit updateProgress(curr, total); } - -private: - PX4FirmwareUpgradeThreadWorker* _worker; - QThread* _workerThread; ///< Thread which PX4FirmwareUpgradeThreadWorker runs on -}; - -#endif