Commit ec67f11b authored by Don Gagne's avatar Don Gagne

Major re-write of Firmware Upgrade

Usability plus, new features:
- 3DR Radio support
- APM Stack support
parent 8e9dd2f7
......@@ -591,8 +591,10 @@ HEADERS+= \
!MobileBuild {
HEADERS += \
src/VehicleSetup/FirmwareUpgradeController.h \
src/VehicleSetup/PX4Bootloader.h \
src/VehicleSetup/PX4FirmwareUpgradeThread.h
src/VehicleSetup/Bootloader.h \
src/VehicleSetup/PX4FirmwareUpgradeThread.h \
src/VehicleSetup/FirmwareImage.h \
}
SOURCES += \
......@@ -621,8 +623,10 @@ SOURCES += \
!MobileBuild {
SOURCES += \
src/VehicleSetup/FirmwareUpgradeController.cc \
src/VehicleSetup/PX4Bootloader.cc \
src/VehicleSetup/PX4FirmwareUpgradeThread.cc
src/VehicleSetup/Bootloader.cc \
src/VehicleSetup/PX4FirmwareUpgradeThread.cc \
src/VehicleSetup/FirmwareImage.cc \
}
# Fact System code
......
......@@ -201,6 +201,12 @@
<file alias="QGroundControlConnect">resources/QGroundControlConnect.svg</file>
</qresource>
<qresource prefix="/res/firmware">
<file alias="px4.png">resources/firmware/px4.png</file>
<file alias="apm.png">resources/firmware/apm.png</file>
<file alias="3drradio.png">resources/firmware/3drradio.png</file>
</qresource>
<qresource prefix="/res/mavs">
<file alias="Helicopter">resources/mavs/helicopter.svg</file>
<file alias="Unknown">resources/mavs/unknown.svg</file>
......
......@@ -27,7 +27,7 @@
#include "QGCLoggingCategory.h"
// Add Global logging categories (not class specific) here using QGC_LOGGING_CATEGORY
// There currently are no global categories
QGC_LOGGING_CATEGORY(FirmwareUpgradeLog, "FirmwareUpgradeLog")
QGCLoggingCategoryRegister* _instance = NULL;
......
......@@ -31,7 +31,7 @@
#include <QStringList>
// Add Global logging categories (not class specific) here using Q_DECLARE_LOGGING_CATEGORY
// There currently are no global categories
Q_DECLARE_LOGGING_CATEGORY(FirmwareUpgradeLog)
/// @def QGC_LOGGING_CATEGORY
/// This is a QGC specific replacement for Q_LOGGING_CATEGORY. It will register the category name into a
......
......@@ -25,7 +25,8 @@
/// @brief PX4 Bootloader Utility routines
/// @author Don Gagne <don@thegagnes.com>
#include "PX4Bootloader.h"
#include "Bootloader.h"
#include "QGCLoggingCategory.h"
#include <QFile>
#include <QSerialPortInfo>
......@@ -78,13 +79,13 @@ static quint32 crc32(const uint8_t *src, unsigned len, unsigned state)
return state;
}
PX4Bootloader::PX4Bootloader(QObject *parent) :
Bootloader::Bootloader(QObject *parent) :
QObject(parent)
{
}
bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxSize)
bool Bootloader::_write(QextSerialPort* port, const uint8_t* data, qint64 maxSize)
{
qint64 bytesWritten = port->write((const char*)data, maxSize);
if (bytesWritten == -1) {
......@@ -101,13 +102,13 @@ bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxS
return true;
}
bool PX4Bootloader::write(QextSerialPort* port, const uint8_t byte)
bool Bootloader::_write(QextSerialPort* port, const uint8_t byte)
{
uint8_t buf[1] = { byte };
return write(port, buf, 1);
return _write(port, buf, 1);
}
bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout)
bool Bootloader::_read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout)
{
qint64 bytesAlreadyRead = 0;
......@@ -137,11 +138,13 @@ bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, in
return true;
}
bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout)
/// Read a PROTO_SYNC command response from the bootloader
/// @param responseTimeout Msecs to wait for response bytes to become available on port
bool Bootloader::_getCommandResponse(QextSerialPort* port, int responseTimeout)
{
uint8_t response[2];
if (!read(port, response, 2, responseTimeout)) {
if (!_read(port, response, 2, responseTimeout)) {
_errorString.prepend("Get Command Response: ");
return false;
}
......@@ -164,18 +167,21 @@ bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout
return true;
}
bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value)
/// Send a PROTO_GET_DEVICE command to retrieve a value from the PX4 bootloader
/// @param param Value to retrieve using INFO_BOARD_* enums
/// @param value Returned value
bool Bootloader::_getPX4BoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value)
{
uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC };
if (!write(port, buf, sizeof(buf))) {
if (!_write(port, buf, sizeof(buf))) {
goto Error;
}
port->flush();
if (!read(port, (uint8_t*)&value, sizeof(value))) {
if (!_read(port, (uint8_t*)&value, sizeof(value))) {
goto Error;
}
if (!getCommandResponse(port)) {
if (!_getCommandResponse(port)) {
goto Error;
}
......@@ -186,15 +192,18 @@ Error:
return false;
}
bool PX4Bootloader::sendCommand(QextSerialPort* port, const uint8_t cmd, int responseTimeout)
/// Send a command to the bootloader
/// @param cmd Command to send using PROTO_* enums
/// @return true: Command sent and valid sync response returned
bool Bootloader::_sendCommand(QextSerialPort* port, const uint8_t cmd, int responseTimeout)
{
uint8_t buf[2] = { cmd, PROTO_EOC };
if (!write(port, buf, 2)) {
if (!_write(port, buf, 2)) {
goto Error;
}
port->flush();
if (!getCommandResponse(port, responseTimeout)) {
if (!_getCommandResponse(port, responseTimeout)) {
goto Error;
}
......@@ -205,10 +214,10 @@ Error:
return false;
}
bool PX4Bootloader::erase(QextSerialPort* port)
bool Bootloader::erase(QextSerialPort* port)
{
// Erase is slow, need larger timeout
if (!sendCommand(port, PROTO_CHIP_ERASE, _eraseTimeout)) {
if (!_sendCommand(port, PROTO_CHIP_ERASE, _eraseTimeout)) {
_errorString = tr("Board erase failed: %1").arg(_errorString);
return false;
}
......@@ -216,11 +225,20 @@ bool PX4Bootloader::erase(QextSerialPort* port)
return true;
}
bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilename)
bool Bootloader::program(QextSerialPort* port, const FirmwareImage* image)
{
QFile firmwareFile(firmwareFilename);
if (image->imageIsBinFormat()) {
return _binProgram(port, image);
} else {
return _ihxProgram(port, image);
}
}
bool Bootloader::_binProgram(QextSerialPort* port, const FirmwareImage* image)
{
QFile firmwareFile(image->binFilename());
if (!firmwareFile.open(QIODevice::ReadOnly)) {
_errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString());
_errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename()).arg(firmwareFile.errorString());
return false;
}
uint32_t imageSize = (uint32_t)firmwareFile.size();
......@@ -248,12 +266,12 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam
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)) {
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)) {
if (_getCommandResponse(port)) {
failed = false;
}
}
......@@ -270,7 +288,7 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam
// 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);
emit updateProgress(bytesSent, imageSize);
}
firmwareFile.close();
......@@ -284,46 +302,131 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam
return true;
}
bool PX4Bootloader::verify(QextSerialPort* port, const QString firmwareFilename)
bool Bootloader::_ihxProgram(QextSerialPort* port, const FirmwareImage* image)
{
uint32_t imageSize = image->imageSize();
uint32_t bytesSent = 0;
for (uint16_t index=0; index<image->ihxBlockCount(); index++) {
bool failed;
uint16_t flashAddress;
QByteArray bytes;
if (!image->ihxGetBlock(index, flashAddress, bytes)) {
_errorString = QString("Unable to retrieve block from ihx: index %1").arg(index);
return false;
}
qCDebug(FirmwareUpgradeLog) << QString("Bootloader::_ihxProgram - address:%1 size:%2 block:%3").arg(flashAddress).arg(bytes.count()).arg(index);
// Set flash address
failed = true;
if (_write(port, PROTO_LOAD_ADDRESS) &&
_write(port, flashAddress & 0xFF) &&
_write(port, (flashAddress >> 8) & 0xFF) &&
_write(port, PROTO_EOC)) {
port->flush();
if (_getCommandResponse(port)) {
failed = false;
}
}
if (failed) {
_errorString = QString("Unable to set flash start address: 0x%2").arg(flashAddress, 8, 16, QLatin1Char('0'));
return false;
}
// Flash
int bytesIndex = 0;
uint16_t bytesLeftToWrite = bytes.count();
while (bytesLeftToWrite > 0) {
uint8_t bytesToWrite;
if (bytesLeftToWrite > PROG_MULTI_MAX) {
bytesToWrite = PROG_MULTI_MAX;
} else {
bytesToWrite = bytesLeftToWrite;
}
failed = true;
if (_write(port, PROTO_PROG_MULTI) &&
_write(port, bytesToWrite) &&
_write(port, &((uint8_t *)bytes.data())[bytesIndex], bytesToWrite) &&
_write(port, PROTO_EOC)) {
port->flush();
if (_getCommandResponse(port)) {
failed = false;
}
}
if (failed) {
_errorString = QString("Flash failed: %1 at address 0x%2").arg(_errorString).arg(flashAddress, 8, 16, QLatin1Char('0'));
return false;
}
bytesIndex += bytesToWrite;
bytesLeftToWrite -= bytesToWrite;
bytesSent += bytesToWrite;
emit updateProgress(bytesSent, imageSize);
}
}
return true;
}
bool Bootloader::verify(QextSerialPort* port, const FirmwareImage* image)
{
bool ret;
if (_bootloaderVersion <= 2) {
ret = _bootloaderVerifyRev2(port, firmwareFilename);
if (!image->imageIsBinFormat() || _bootloaderVersion <= 2) {
ret = _verifyBytes(port, image);
} else {
ret = _bootloaderVerifyRev3(port);
ret = _verifyCRC(port);
}
sendBootloaderReboot(port);
reboot(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)
/// @brief Verify the flash on bootloader eading it back and comparing it against the original image->
bool Bootloader::_verifyBytes(QextSerialPort* port, const FirmwareImage* image)
{
QFile firmwareFile(firmwareFilename);
if (image->imageIsBinFormat()) {
return _binVerifyBytes(port, image);
} else {
return _ihxVerifyBytes(port, image);
}
}
bool Bootloader::_binVerifyBytes(QextSerialPort* port, const FirmwareImage* image)
{
Q_ASSERT(image->imageIsBinFormat());
QFile firmwareFile(image->binFilename());
if (!firmwareFile.open(QIODevice::ReadOnly)) {
_errorString = tr("Unable to open firmware file %1: %2").arg(firmwareFilename).arg(firmwareFile.errorString());
_errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename()).arg(firmwareFile.errorString());
return false;
}
uint32_t imageSize = (uint32_t)firmwareFile.size();
if (!sendCommand(port, PROTO_CHIP_VERIFY)) {
if (!_sendCommand(port, PROTO_CHIP_VERIFY)) {
return false;
}
uint8_t fileBuf[READ_MULTI_MAX];
uint8_t flashBuf[READ_MULTI_MAX];
uint8_t readBuf[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);
if (bytesToRead > (int)sizeof(readBuf)) {
bytesToRead = (int)sizeof(readBuf);
}
Q_ASSERT((bytesToRead % 4) == 0);
......@@ -337,48 +440,137 @@ bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString fi
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 (_write(port, PROTO_READ_MULTI) &&
_write(port, (uint8_t)bytesToRead) &&
_write(port, PROTO_EOC)) {
port->flush();
if (_read(port, readBuf, sizeof(readBuf))) {
if (_getCommandResponse(port)) {
failed = false;
}
}
}
if (failed) {
_errorString = tr("Verify failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0'));
_errorString = tr("Read failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0'));
return false;
}
for (int i=0; i<bytesToRead; i++) {
if (fileBuf[i] != flashBuf[i]) {
_errorString = tr("Compare failed at %1: file(0x%2) flash(0x%3) at address: 0x%4").arg(bytesVerified + i).arg(fileBuf[i], 2, 16, QLatin1Char('0')).arg(flashBuf[i], 2, 16, QLatin1Char('0')).arg(bytesVerified, 8, 16, QLatin1Char('0'));
if (fileBuf[i] != readBuf[i]) {
_errorString = tr("Compare failed: expected(0x%1) actual(0x%2) at address: 0x%3").arg(fileBuf[i], 2, 16, QLatin1Char('0')).arg(readBuf[i], 2, 16, QLatin1Char('0')).arg(bytesVerified + i, 8, 16, QLatin1Char('0'));
return false;
}
}
bytesVerified += bytesToRead;
emit updateProgress(bytesVerified, imageSize);
}
firmwareFile.close();
return true;
}
/// @Brief Verify the flash on a version 3 or higher bootloader board by comparing CRCs.
bool PX4Bootloader::_bootloaderVerifyRev3(QextSerialPort* port)
bool Bootloader::_ihxVerifyBytes(QextSerialPort* port, const FirmwareImage* image)
{
Q_ASSERT(!image->imageIsBinFormat());
uint32_t imageSize = image->imageSize();
uint32_t bytesVerified = 0;
for (uint16_t index=0; index<image->ihxBlockCount(); index++) {
bool failed;
uint16_t readAddress;
QByteArray imageBytes;
if (!image->ihxGetBlock(index, readAddress, imageBytes)) {
_errorString = QString("Unable to retrieve block from ihx: index %1").arg(index);
return false;
}
qCDebug(FirmwareUpgradeLog) << QString("Bootloader::_ihxVerifyBytes - address:%1 size:%2 block:%3").arg(readAddress).arg(imageBytes.count()).arg(index);
// Set read address
failed = true;
if (_write(port, PROTO_LOAD_ADDRESS) &&
_write(port, readAddress & 0xFF) &&
_write(port, (readAddress >> 8) & 0xFF) &&
_write(port, PROTO_EOC)) {
port->flush();
if (_getCommandResponse(port)) {
failed = false;
}
}
if (failed) {
_errorString = QString("Unable to set read start address: 0x%2").arg(readAddress, 8, 16, QLatin1Char('0'));
return false;
}
// Read back
int bytesIndex = 0;
uint16_t bytesLeftToRead = imageBytes.count();
while (bytesLeftToRead > 0) {
uint8_t bytesToRead;
uint8_t readBuf[READ_MULTI_MAX];
if (bytesLeftToRead > READ_MULTI_MAX) {
bytesToRead = READ_MULTI_MAX;
} else {
bytesToRead = bytesLeftToRead;
}
failed = true;
if (_write(port, PROTO_READ_MULTI) &&
_write(port, bytesToRead) &&
_write(port, PROTO_EOC)) {
port->flush();
if (_read(port, readBuf, bytesToRead)) {
if (_getCommandResponse(port)) {
failed = false;
}
}
}
if (failed) {
_errorString = tr("Read failed: %1 at address: 0x%2").arg(_errorString).arg(readAddress, 8, 16, QLatin1Char('0'));
return false;
}
// Compare
for (int i=0; i<bytesToRead; i++) {
if ((uint8_t)imageBytes[bytesIndex + i] != readBuf[i]) {
_errorString = QString("Compare failed: expected(0x%1) actual(0x%2) at address: 0x%3").arg(imageBytes[bytesIndex + i], 2, 16, QLatin1Char('0')).arg(readBuf[i], 2, 16, QLatin1Char('0')).arg(readAddress + i, 8, 16, QLatin1Char('0'));
return false;
}
}
bytesVerified += bytesToRead;
bytesIndex += bytesToRead;
bytesLeftToRead -= bytesToRead;
emit updateProgress(bytesVerified, imageSize);
}
}
return true;
}
/// @Brief Verify the flash by comparing CRCs.
bool Bootloader::_verifyCRC(QextSerialPort* port)
{
uint8_t buf[2] = { PROTO_GET_CRC, PROTO_EOC };
quint32 flashCRC;
bool failed = true;
if (write(port, buf, 2)) {
if (_write(port, buf, 2)) {
port->flush();
if (read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) {
if (getCommandResponse(port)) {
if (_read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) {
if (_getCommandResponse(port)) {
failed = false;
}
}
......@@ -395,7 +587,7 @@ bool PX4Bootloader::_bootloaderVerifyRev3(QextSerialPort* port)
return true;
}
bool PX4Bootloader::open(QextSerialPort* port, const QString portName)
bool Bootloader::open(QextSerialPort* port, const QString portName)
{
Q_ASSERT(!port->isOpen());
......@@ -414,10 +606,10 @@ bool PX4Bootloader::open(QextSerialPort* port, const QString portName)
return true;
}
bool PX4Bootloader::sync(QextSerialPort* port)
bool Bootloader::sync(QextSerialPort* port)
{
// Send sync command
if (sendCommand(port, PROTO_GET_SYNC)) {
if (_sendCommand(port, PROTO_GET_SYNC)) {
return true;
} else {
_errorString.prepend("Sync: ");
......@@ -425,10 +617,10 @@ bool PX4Bootloader::sync(QextSerialPort* port)
}
}
bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize)
bool Bootloader::getPX4BoardInfo(QextSerialPort* port, uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize)
{
if (!getBoardInfo(port, INFO_BL_REV, _bootloaderVersion)) {
if (!_getPX4BoardInfo(port, INFO_BL_REV, _bootloaderVersion)) {
goto Error;
}
if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) {
......@@ -436,11 +628,11 @@ bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersi
goto Error;
}
if (!getBoardInfo(port, INFO_BOARD_ID, _boardID)) {
if (!_getPX4BoardInfo(port, INFO_BOARD_ID, _boardID)) {
goto Error;
}
if (!getBoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) {
if (!_getPX4BoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) {
qWarning() << _errorString;
goto Error;
}
......@@ -456,7 +648,35 @@ Error:
return false;
}
bool PX4Bootloader::sendBootloaderReboot(QextSerialPort* port)
bool Bootloader::get3DRRadioBoardId(QextSerialPort* port, uint32_t& boardID)
{
uint8_t buf[2] = { PROTO_GET_DEVICE, PROTO_EOC };
if (!_write(port, buf, sizeof(buf))) {
goto Error;
}
port->flush();
if (!_read(port, (uint8_t*)buf, 2)) {
goto Error;
}
if (!_getCommandResponse(port)) {
goto Error;
}
boardID = buf[0];
_bootloaderVersion = 0;
_boardFlashSize = 0;
return true;
Error:
_errorString.prepend("Get Board Id: ");
return false;
}
bool Bootloader::reboot(QextSerialPort* port)
{
return write(port, PROTO_BOOT) && write(port, PROTO_EOC);
return _write(port, PROTO_BOOT) && _write(port, PROTO_EOC);
}
......@@ -22,126 +22,121 @@
======================================================================*/
/// @file
/// @brief PX4 Bootloader Utility routines
/// @author Don Gagne <don@thegagnes.com>
#ifndef PX4Bootloader_H
#define PX4Bootloader_H
#ifndef Bootloader_H
#define Bootloader_H
#include "FirmwareImage.h"
#include "qextserialport.h"
#include <stdint.h>
/// @brief This class is used to communicate with the Bootloader.
class PX4Bootloader : public QObject
/// Bootloader Utility routines. Works with PX4 bootloader and 3DR Radio bootloader.
class Bootloader : public QObject
{
Q_OBJECT
public:
explicit PX4Bootloader(QObject *parent = 0);
explicit Bootloader(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 Opens a port to the bootloader
bool open(QextSerialPort* port, const QString portName);
/// @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
/// @brief Erases the current program
bool erase(QextSerialPort* port);
/// @brief Program the board with the specified image
bool program(QextSerialPort* port, const FirmwareImage* image);
/// @brief Verify the board flash.
bool verify(QextSerialPort* port, const FirmwareImage* image);
/// @brief Retrieve a set of board info from the bootloader of PX4 FMU and PX4 Flow boards
/// @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);
bool getPX4BoardInfo(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 Retrieve the board id from a 3DR Radio
bool get3DRRadioBoardId(QextSerialPort* port, uint32_t& boardID);
/// @brief Sends a PROTO_REBOOT command to the bootloader
bool sendBootloaderReboot(QextSerialPort* port);
bool reboot(QextSerialPort* port);
/// @brief Sends a PROTO_ERASE command to the bootlader
bool erase(QextSerialPort* port);
// Supported bootloader board ids
static const int boardIDPX4FMUV1 = 5; ///< PX4 V1 board
static const int boardIDPX4FMUV2 = 9; ///< PX4 V2 board
static const int boardIDPX4Flow = 6; ///< PX4 Flow board
static const int boardIDAeroCore = 98; ///< Gumstix AeroCore board
static const int boardID3DRRadio = 78; ///< 3DR Radio
signals:
/// @brief Signals progress indicator for long running bootloader utility routines
void updateProgramProgress(int curr, int total);
void updateProgress(int curr, int total);
private:
bool _binProgram(QextSerialPort* port, const FirmwareImage* image);
bool _ihxProgram(QextSerialPort* port, const FirmwareImage* image);
bool _write(QextSerialPort* port, const uint8_t* data, qint64 maxSize);
bool _write(QextSerialPort* port, const uint8_t byte);
bool _read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout = _readTimout);
bool _sendCommand(QextSerialPort* port, uint8_t cmd, int responseTimeout = _responseTimeout);
bool _getCommandResponse(QextSerialPort* port, const int responseTimeout = _responseTimeout);
bool _getPX4BoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value);
bool _verifyBytes(QextSerialPort* port, const FirmwareImage* image);
bool _binVerifyBytes(QextSerialPort* port, const FirmwareImage* image);
bool _ihxVerifyBytes(QextSerialPort* port, const FirmwareImage* image);
bool _verifyCRC(QextSerialPort* port);
enum {
// protocol bytes
PROTO_INSYNC = 0x12, ///< 'in sync' byte sent before status
PROTO_EOC = 0x20, ///< end of command
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
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
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_LOAD_ADDRESS = 0x24, ///< set next programming 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
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
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
PROG_MULTI_MAX = 64, ///< write size for PROTO_PROG_MULTI, must be multiple of 4
READ_MULTI_MAX = 255 ///< 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);
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
......
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/// @file
/// @brief Support for Intel Hex firmware file
/// @author Don Gagne <don@thegagnes.com>
#include "FirmwareImage.h"
#include "QGCLoggingCategory.h"
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QFileInfo>
#include <QDir>
FirmwareImage::FirmwareImage(QObject* parent) :
QObject(parent),
_imageSize(0)
{
}
bool FirmwareImage::load(const QString& imageFilename, uint32_t boardId)
{
_imageSize = 0;
_boardId = boardId;
if (imageFilename.endsWith(".bin")) {
return _binLoad(imageFilename);
_binFormat = true;
return true;
} else if (imageFilename.endsWith(".px4")) {
_binFormat = true;
return _px4Load(imageFilename);
} else if (imageFilename.endsWith(".ihx")) {
_binFormat = false;
return _ihxLoad(imageFilename);
} else {
emit errorMessage("Unsupported file format");
return false;
}
}
bool FirmwareImage::_readByteFromStream(QTextStream& stream, uint8_t& byte)
{
QString hex = stream.read(2);
if (hex.count() != 2) {
return false;
}
bool success;
byte = (uint8_t)hex.toInt(&success, 16);
return success;
}
bool FirmwareImage::_readWordFromStream(QTextStream& stream, uint16_t& word)
{
QString hex = stream.read(4);
if (hex.count() != 4) {
return false;
}
bool success;
word = (uint16_t)hex.toInt(&success, 16);
return success;
}
bool FirmwareImage::_readBytesFromStream(QTextStream& stream, uint8_t byteCount, QByteArray& bytes)
{
bytes.clear();
while (byteCount) {
uint8_t byte;
if (!_readByteFromStream(stream, byte)) {
return false;
}
bytes += byte;
byteCount--;
}
return true;
}
bool FirmwareImage::_ihxLoad(const QString& ihxFilename)
{
_imageSize = 0;
_ihxBlocks.clear();
QFile ihxFile(ihxFilename);
if (!ihxFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
emit errorMessage(QString("Unable to open firmware file %1, error: %2").arg(ihxFilename).arg(ihxFile.errorString()));
return false;
}
QTextStream stream(&ihxFile);
while (true) {
if (stream.read(1) != ":") {
emit errorMessage("Incorrectly formatted .ihx file, line does not begin with :");
return false;
}
uint8_t blockByteCount;
uint16_t address;
uint8_t recordType;
QByteArray bytes;
uint8_t crc;
if (!_readByteFromStream(stream, blockByteCount) ||
!_readWordFromStream(stream, address) ||
!_readByteFromStream(stream, recordType) ||
!_readBytesFromStream(stream, blockByteCount, bytes) ||
!_readByteFromStream(stream, crc)) {
emit errorMessage("Incorrectly formatted line in .ihx file, line too short");
return false;
}
if (!(recordType == 0 || recordType == 1)) {
emit errorMessage(QString("Unsupported record type in file: %1").arg(recordType));
return false;
}
if (recordType == 0) {
bool appendToLastBlock = false;
// Can we append this block to the last one?
if (_ihxBlocks.count()) {
int lastBlockIndex = _ihxBlocks.count() - 1;
if (_ihxBlocks[lastBlockIndex].address + _ihxBlocks[lastBlockIndex].bytes.count() == address) {
appendToLastBlock = true;
}
}
if (appendToLastBlock) {
_ihxBlocks[_ihxBlocks.count() - 1].bytes += bytes;
qCDebug(FirmwareUpgradeLog) << QString("_ihxLoad - append - address:%1 size:%2 block:%3").arg(address).arg(blockByteCount).arg(ihxBlockCount());
} else {
IntelHexBlock_t block;
block.address = address;
block.bytes = bytes;
_ihxBlocks += block;
qCDebug(FirmwareUpgradeLog) << QString("_ihxLoad - new block - address:%1 size:%2 block:%3").arg(address).arg(blockByteCount).arg(ihxBlockCount());
}
_imageSize += blockByteCount;
} else if (recordType == 1) {
// EOF
qCDebug(FirmwareUpgradeLog) << QString("_ihxLoad - EOF");
break;
}
// Move to next line
stream.readLine();
}
ihxFile.close();
return true;
}
bool FirmwareImage::_px4Load(const QString& imageFilename)
{
_imageSize = 0;
// We need to collect information from the .px4 file as well as pull the binary image out to a seperate file.
QFile px4File(imageFilename);
if (!px4File.open(QIODevice::ReadOnly | QIODevice::Text)) {
emit errorMessage(QString("Unable to open firmware file %1, error: %2").arg(imageFilename).arg(px4File.errorString()));
return false;
}
QByteArray bytes = px4File.readAll();
px4File.close();
QJsonDocument doc = QJsonDocument::fromJson(bytes);
if (doc.isNull()) {
emit errorMessage("Supplied file is not a valid JSON document");
return false;
}
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<sizeof(rgJsonKeys)/sizeof(rgJsonKeys[0]); i++) {
if (!px4Json.contains(rgJsonKeys[i])) {
emit errorMessage(QString("Incorrectly formatted firmware file. No %1 key.").arg(rgJsonKeys[i]));
return false;
}
}
uint32_t firmwareBoardId = (uint32_t)px4Json.value(QString("board_id")).toInt();
if (firmwareBoardId != _boardId) {
emit errorMessage(QString("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardId).arg(_boardId));
return false;
}
// Decompress the parameter xml and save to file
QByteArray decompressedBytes;
bool success = _decompressJsonValue(px4Json, // JSON object
bytes, // Raw bytes of JSON document
"parameter_xml_size", // key which holds byte size
"parameter_xml", // key which holds compress bytes
decompressedBytes); // Returned decompressed bytes
if (success) {
// We cache the parameter xml in the same location as settings
QSettings settings;
QDir parameterDir = QFileInfo(settings.fileName()).dir();
QString parameterFilename = parameterDir.filePath("PX4ParameterFactMetaData.xml");
qDebug() << parameterFilename;
QFile parameterFile(parameterFilename);
if (parameterFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qint64 bytesWritten = parameterFile.write(decompressedBytes);
if (bytesWritten != decompressedBytes.count()) {
// FIXME: What about these warnings?
emit statusMessage(QString("Write failed for parameter meta data file, error: %1").arg(parameterFile.errorString()));
parameterFile.close();
QFile::remove(parameterFilename);
} else {
parameterFile.close();
}
} else {
emit statusMessage(QString("Unable to open parameter meta data file %1 for writing, error: %2").arg(parameterFilename).arg(parameterFile.errorString()));
}
}
// Decompress the image and save to file
_imageSize = px4Json.value(QString("image_size")).toInt();
success = _decompressJsonValue(px4Json, // JSON object
bytes, // Raw bytes of JSON document
"image_size", // key which holds byte size
"image", // key which holds compress bytes
decompressedBytes); // Returned decompressed bytes
if (!success) {
return false;
}
// Pad image to 4-byte boundary
while ((decompressedBytes.count() % 4) != 0) {
decompressedBytes.append(static_cast<char>(static_cast<unsigned char>(0xFF)));
}
// Store decompressed image file in same location as original download file
QDir imageDir = QFileInfo(imageFilename).dir();
QString decompressFilename = imageDir.filePath("PX4FlashUpgrade.bin");
QFile decompressFile(decompressFilename);
if (!decompressFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
emit errorMessage(QString("Unable to open decompressed file %1 for writing, error: %2").arg(decompressFilename).arg(decompressFile.errorString()));
return false;
}
qint64 bytesWritten = decompressFile.write(decompressedBytes);
if (bytesWritten != decompressedBytes.count()) {
emit errorMessage(QString("Write failed for decompressed image file, error: %1").arg(decompressFile.errorString()));
return false;
}
decompressFile.close();
_binFilename = decompressFilename;
return true;
}
/// Decompress a set of bytes stored in a Json document.
bool FirmwareImage::_decompressJsonValue(const QJsonObject& jsonObject, ///< JSON object
const QByteArray& jsonDocBytes, ///< Raw bytes of JSON document
const QString& sizeKey, ///< key which holds byte size
const QString& bytesKey, ///< key which holds compress bytes
QByteArray& decompressedBytes) ///< Returned decompressed bytes
{
// Validate decompressed size key
if (!jsonObject.contains(sizeKey)) {
emit statusMessage(QString("Firmware file missing %1 key").arg(sizeKey));
return false;
}
int decompressedSize = jsonObject.value(QString(sizeKey)).toInt();
if (decompressedSize == 0) {
emit errorMessage(QString("Firmware file has invalid decompressed size for %1").arg(sizeKey));
return false;
}
// 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 parts = QString(jsonDocBytes).split(QString("\"%1\": \"").arg(bytesKey));
if (parts.count() == 1) {
emit errorMessage(QString("Could not find compressed bytes for %1 in Firmware file").arg(bytesKey));
return false;
}
parts = parts.last().split("\"");
if (parts.count() == 1) {
emit errorMessage(QString("Incorrectly formed compressed bytes section for %1 in Firmware file").arg(bytesKey));
return false;
}
// Store decompressed size as first four bytes. This is required by qUncompress routine.
QByteArray raw;
raw.append((unsigned char)((decompressedSize >> 24) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 16) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 8) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 0) & 0xFF));
QByteArray raw64 = parts.first().toUtf8();
raw.append(QByteArray::fromBase64(raw64));
decompressedBytes = qUncompress(raw);
if (decompressedBytes.count() == 0) {
emit errorMessage(QString("Firmware file has 0 length %1").arg(bytesKey));
return false;
}
if (decompressedBytes.count() != decompressedSize) {
emit errorMessage(QString("Size for decompressed %1 does not match stored size: Expected(%1) Actual(%2)").arg(decompressedSize).arg(decompressedBytes.count()));
return false;
}
emit statusMessage(QString("Succesfully decompressed %1").arg(bytesKey));
return true;
}
uint16_t FirmwareImage::ihxBlockCount(void) const
{
return _ihxBlocks.count();
}
bool FirmwareImage::ihxGetBlock(uint16_t index, uint16_t& address, QByteArray& bytes) const
{
address = 0;
bytes.clear();
if (index < ihxBlockCount()) {
address = _ihxBlocks[index].address;
bytes = _ihxBlocks[index].bytes;
return true;
} else {
return false;
}
}
bool FirmwareImage::_binLoad(const QString& imageFilename)
{
QFile binFile(imageFilename);
if (!binFile.open(QIODevice::ReadOnly)) {
emit errorMessage(QString("Unabled to open firmware file %1, %2").arg(imageFilename).arg(binFile.errorString()));
return false;
}
_imageSize = (uint32_t)binFile.size();
binFile.close();
return true;
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/// @file
/// @author Don Gagne <don@thegagnes.com>
#ifndef FirmwareImage_H
#define FirmwareImage_H
#include <QObject>
#include <QString>
#include <QByteArray>
#include <QList>
#include <QTextStream>
#include <stdint.h>
/// Support for Intel Hex firmware file
class FirmwareImage : public QObject
{
Q_OBJECT
public:
FirmwareImage(QObject *parent = 0);
/// Loads the specified image file. Supported formats: .px4, .bin, .ihx.
/// Emits errorMesssage and statusMessage signals while loading.
/// @param imageFilename Image file to load
/// @param boardId Board id that we are going to load this image onto
/// @return true: success, false: failure
bool load(const QString& imageFilename, uint32_t boardId);
/// Returns the number of bytes in the image.
uint32_t imageSize(void) const { return _imageSize; }
/// @return true: image format is .bin
bool imageIsBinFormat(void) const { return _binFormat; }
/// @return Filename for .bin file
QString binFilename(void) const { return _binFilename; }
/// @return Block count from .ihx image
uint16_t ihxBlockCount(void) const;
/// Retrieves the specified block from the .ihx image
/// @param index Index of block to return
/// @param address Address of returned block
/// @param byets Bytes of returned block
/// @return true: block retrieved
bool ihxGetBlock(uint16_t index, uint16_t& address, QByteArray& bytes) const;
signals:
void errorMessage(const QString& errorString);
void statusMessage(const QString& warningtring);
private:
bool _binLoad(const QString& px4Filename);
bool _px4Load(const QString& px4Filename);
bool _ihxLoad(const QString& ihxFilename);
bool _readByteFromStream(QTextStream& stream, uint8_t& byte);
bool _readWordFromStream(QTextStream& stream, uint16_t& word);
bool _readBytesFromStream(QTextStream& stream, uint8_t byteCount, QByteArray& bytes);
bool _decompressJsonValue(const QJsonObject& jsonObject,
const QByteArray& jsonDocBytes,
const QString& sizeKey,
const QString& bytesKey,
QByteArray& decompressedBytes);
typedef struct {
uint16_t address;
QByteArray bytes;
} IntelHexBlock_t;
bool _binFormat;
uint32_t _boardId;
QString _binFilename;
QList<IntelHexBlock_t> _ihxBlocks;
uint32_t _imageSize;
};
#endif
......@@ -34,158 +34,337 @@ import QGroundControl.Controllers 1.0
import QGroundControl.ScreenTools 1.0
QGCView {
viewPanel: panel
id: qgcView
viewPanel: panel
// User visible strings
readonly property string title: "FIRMWARE UPDATE"
readonly property string highlightPrefix: "<font color=\"yellow\">"
readonly property string highlightSuffix: "</font>"
readonly property string welcomeText: "QGroundControl can upgrade the firmware on Pixhawk devices, 3DR Radios and PX4 Flow Smart Cameras."
readonly property string plugInText: highlightPrefix + "Plug in your device" + highlightSuffix + " via USB to " + highlightPrefix + "start" + highlightSuffix + " firmware upgrade"
readonly property string qgcDisconnectText: "All QGroundControl connections to vehicles must be disconnected prior to firmware upgrade. " +
"Click " + highlightPrefix + "Disconnect" + highlightSuffix + " in the toolbar above."
property string usbUnplugText: "Device must be disconnected from USB to start firmware upgrade. " +
highlightPrefix + "Disconnect {0}" + highlightSuffix + " from usb."
property string firmwareWarningMessage
property bool controllerCompleted: false
property bool initialBoardSearch: true
property string firmwareName
function cancelFlash() {
statusTextArea.append(highlightPrefix + "Upgrade cancelled" + highlightSuffix)
statusTextArea.append("------------------------------------------")
controller.cancel()
flashCompleteWaitTimer.running = true
}
QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled }
FirmwareUpgradeController {
id: controller
upgradeButton: upgradeButton
progressBar: progressBar
statusLog: statusTextArea
firmwareType: FirmwareUpgradeController.StableFirmware
onShowMessage: {
showMessage(title, message, StandardButton.Ok)
Component.onCompleted: {
controllerCompleted = true
if (qgcView.completedSignalled) {
// We can only start the board search when the Qml and Controller are completely done loading
controller.startBoardSearch()
}
}
onNoBoardFound: {
initialBoardSearch = false
statusTextArea.append(plugInText)
}
onBoardGone: {
initialBoardSearch = false
statusTextArea.append(plugInText)
}
onBoardFound: {
if (initialBoardSearch) {
// Board was found right away, so something is already plugged in before we've started upgrade
if (controller.qgcConnections) {
statusTextArea.append(qgcDisconnectText)
} else {
statusTextArea.append(usbUnplugText.replace('{0}', controller.boardType))
}
} else {
// We end up here when we detect a board plugged in after we've started upgrade
statusTextArea.append(highlightPrefix + "Found device" + highlightSuffix + ": " + controller.boardType)
if (controller.boardType == "Pixhawk") {
showDialog(pixhawkFirmwareSelectDialog, title, 50, StandardButton.Ok | StandardButton.Cancel)
}
}
}
onError: {
hideDialog()
flashCompleteWaitTimer.running = true
}
onFlashComplete: flashCompleteWaitTimer.running = true
}
QGCViewPanel {
id: panel
anchors.fill: parent
onCompleted: {
if (controllerCompleted) {
// We can only start the board search when the Qml and Controller are completely done loading
controller.startBoardSearch()
}
}
Component {
id: firmwareWarningComponent
// After a flash completes we start this timer to trigger resetting the ui back to it's initial state of being ready to
// flash another board. We do this only after the timer triggers to leave the results of the previous flash on the screen
// for a small amount amount of time.
QGCViewMessage {
message: firmwareWarningMessage
Timer {
id: flashCompleteWaitTimer
interval: 15000
function accept() {
hideDialog()
controller.doFirmwareUpgrade();
}
}
onTriggered: {
initialBoardSearch = true
progressBar.value = 0
statusTextArea.append(welcomeText)
controller.startBoardSearch()
}
}
Component {
id: pixhawkFirmwareSelectDialog
Column {
QGCViewDialog {
anchors.fill: parent
property bool showVersionSelection: apmFlightStack.checked || advancedMode.checked
QGCLabel {
text: "FIRMWARE UPDATE"
font.pixelSize: ScreenTools.largeFontPixelSize
function accept() {
hideDialog()
controller.flash(firmwareVersionCombo.model.get(firmwareVersionCombo.currentIndex).firmwareType)
}
function reject() {
cancelFlash()
hideDialog()
}
ExclusiveGroup {
id: firmwareGroup
}
ListModel {
id: px4FirmwareTypeList
ListElement {
text: qsTr("Standard Version (stable)");
firmwareType: FirmwareUpgradeController.PX4StableFirmware
}
ListElement {
text: qsTr("Beta Testing (beta)");
firmwareType: FirmwareUpgradeController.PX4BetaFirmware
}
ListElement {
text: qsTr("Developer Build (master)");
firmwareType: FirmwareUpgradeController.PX4DeveloperFirmware
}
ListElement {
text: qsTr("Custom firmware file...");
firmwareType: FirmwareUpgradeController.PX4CustomFirmware
}
}
ListModel {
id: apmFirmwareTypeList
Item {
// Just used as a spacer
height: 20
width: 10
ListElement {
text: "ArduCopter Quad"
firmwareType: FirmwareUpgradeController.ApmArduCopterQuadFirmware
}
ListElement {
text: "ArduCopter X8"
firmwareType: FirmwareUpgradeController.ApmArduCopterX8Firmware
}
ListElement {
text: "ArduCopter Hexa"
firmwareType: FirmwareUpgradeController.ApmArduCopterHexaFirmware
}
ListElement {
text: "ArduCopter Octo"
firmwareType: FirmwareUpgradeController.ApmArduCopterOctoFirmware
}
ListElement {
text: "ArduCopter Y"
firmwareType: FirmwareUpgradeController.ApmArduCopterYFirmware
}
ListElement {
text: "ArduCopter Y6"
firmwareType: FirmwareUpgradeController.ApmArduCopterY6Firmware
}
ListElement {
text: "ArduCopter Heli"
firmwareType: FirmwareUpgradeController.ApmArduCopterHeliFirmware
}
ListElement {
text: "ArduPlane"
firmwareType: FirmwareUpgradeController.ApmArduPlaneFirmware
}
ListElement {
text: "Rover"
firmwareType: FirmwareUpgradeController.ApmRoverFirmware
}
}
Row {
spacing: 10
ListModel {
id: firmwareItems
ListElement {
text: qsTr("Standard Version (stable)");
firmwareType: FirmwareUpgradeController.StableFirmware
}
ListElement {
text: qsTr("Beta Testing (beta)");
firmwareType: FirmwareUpgradeController.BetaFirmware
}
ListElement {
text: qsTr("Developer Build (master)");
firmwareType: FirmwareUpgradeController.DeveloperFirmware
}
ListElement {
text: qsTr("Custom firmware file...");
firmwareType: FirmwareUpgradeController.CustomFirmware
}
Column {
anchors.fill: parent
spacing: defaultTextHeight
QGCLabel {
width: parent.width
wrapMode: Text.WordWrap
text: "Detected Pixhawk board. You can select from the following flight stacks:"
}
QGCComboBox {
id: firmwareCombo
width: 200
height: upgradeButton.height
model: firmwareItems
}
QGCButton {
id: upgradeButton
text: "UPGRADE"
primary: true
onClicked: {
if (controller.activeQGCConnections()) {
showMessage("Firmware Upgrade",
"There are still vehicles connected to QGroundControl. " +
"You must disconnect all vehicles from QGroundControl prior to Firmware Upgrade.",
StandardButton.Ok)
return
}
function firmwareVersionChanged(model) {
firmwareVersionWarningLabel.visible = false
// All of this bizarre, setting model to null and index to 1 and then to 0 is to work around
// strangeness in the combo box implementation. This sequence of steps correctly changes the combo model
// without generating any warnings and correctly updates the combo text with the new selection.
firmwareVersionCombo.model = null
firmwareVersionCombo.model = model
firmwareVersionCombo.currentIndex = 1
firmwareVersionCombo.currentIndex = 0
}
if (controller.pluggedInBoard()) {
showMessage("Firmware Upgrade",
"You vehicle is currently connected via USB. " +
"You must unplug your vehicle from USB prior to Firmware Upgrade.",
StandardButton.Ok)
return
}
QGCRadioButton {
id: px4FlightStack
checked: true
exclusiveGroup: firmwareGroup
text: "PX4 Flight Stack (full QGC support)"
onClicked: parent.firmwareVersionChanged(px4FirmwareTypeList)
}
QGCRadioButton {
id: apmFlightStack
exclusiveGroup: firmwareGroup
text: "APM Flight Stack (partial QGC support)"
controller.firmwareType = firmwareItems.get(firmwareCombo.currentIndex).firmwareType
if (controller.firmwareType == 1) {
firmwareWarningMessage = "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" +
"Click Cancel to abort upgrade, Click Ok to Upgrade anwyay"
showDialog(firmwareWarningComponent, "Firmware Upgrade", 50, StandardButton.Cancel | StandardButton.Ok)
} else if (controller.firmwareType == 2) {
firmwareWarningMessage = "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" +
"Click Cancel to abort upgrade, Click Ok to Upgrade anwyay"
showDialog(firmwareWarningComponent, "Firmware Upgrade", 50, StandardButton.Cancel | StandardButton.Ok)
} else {
controller.doFirmwareUpgrade();
onClicked: parent.firmwareVersionChanged(apmFirmwareTypeList)
}
QGCLabel {
width: parent.width
wrapMode: Text.WordWrap
visible: showVersionSelection
text: "Select which version of the above flight stack you would like to install:"
}
QGCComboBox {
id: firmwareVersionCombo
width: 200
visible: showVersionSelection
model: px4FirmwareTypeList
onActivated: {
if (model.get(index).firmwareType == FirmwareUpgradeController.PX4BetaFirmware) {
firmwareVersionWarningLabel.visible = true
firmwareVersionWarningLabel.text = "WARNING: BETA FIRMWARE. " +
"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."
} else if (model.get(index).firmwareType == FirmwareUpgradeController.PX4DeveloperFirmware) {
firmwareVersionWarningLabel.visible = true
firmwareVersionWarningLabel.text = "WARNING: CONTINUOUS BUILD FIRMWARE. " +
"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."
} else {
firmwareVersionWarningLabel.visible = false
}
}
}
}
QGCLabel {
id: firmwareVersionWarningLabel
width: parent.width
wrapMode: Text.WordWrap
visible: false
}
}
QGCCheckBox {
id: advancedMode
anchors.bottom: parent.bottom
text: "Advanced mode"
onClicked: {
firmwareVersionCombo.currentIndex = 0
firmwareVersionWarningLabel.visible = false
}
}
Item {
// Just used as a spacer
height: 20
width: 10
QGCButton {
anchors.leftMargin: ScreenTools.defaultFontPixelWidth * 2
anchors.left: advancedMode.right
anchors.bottom: parent.bottom
text: "Help me pick a flight stack"
onClicked: Qt.openUrlExternally("http://pixhawk.org/choice")
}
} // QGCViewDialog
} // Component - pixhawkFirmwareSelectDialog
Component {
id: firmwareWarningDialog
QGCViewMessage {
message: firmwareWarningMessage
ProgressBar {
id: progressBar
width: parent.width
function accept() {
hideDialog()
controller.doFirmwareUpgrade();
}
}
}
QGCViewPanel {
id: panel
anchors.fill: parent
TextArea {
id: statusTextArea
QGCLabel {
id: titleLabel
text: title
font.pixelSize: ScreenTools.largeFontPixelSize
}
width: parent.width
height: 300
readOnly: true
frameVisible: false
font.pixelSize: ScreenTools.defaultFontPixelSize
text: qsTr("Please disconnect all vehicles from QGroundControl before selecting Upgrade.")
ProgressBar {
id: progressBar
anchors.topMargin: ScreenTools.defaultFontPixelHeight
anchors.top: titleLabel.bottom
width: parent.width
}
style: TextAreaStyle {
textColor: qgcPal.text
backgroundColor: qgcPal.windowShade
}
TextArea {
id: statusTextArea
anchors.topMargin: ScreenTools.defaultFontPixelHeight
anchors.top: progressBar.bottom
anchors.bottom: parent.bottom
width: parent.width
readOnly: true
frameVisible: false
font.pixelSize: ScreenTools.defaultFontPixelSize
textFormat: TextEdit.RichText
text: welcomeText
style: TextAreaStyle {
textColor: qgcPal.text
backgroundColor: qgcPal.windowShade
}
} // Column
} // QGCViewPanel
} // QGCView
}
} // QGCViewPabel
} // QGCView
\ No newline at end of file
......@@ -26,16 +26,7 @@
/// @author Don Gagne <don@thegagnes.com>
#include "FirmwareUpgradeController.h"
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include <QQmlProperty>
#include <QDebug>
#include "Bootloader.h"
#include "QGCFileDialog.h"
#include "QGCMessageBox.h"
......@@ -43,187 +34,229 @@
FirmwareUpgradeController::FirmwareUpgradeController(void) :
_downloadManager(NULL),
_downloadNetworkReply(NULL),
_firmwareType(StableFirmware),
_upgradeButton(NULL),
_statusLog(NULL)
_statusLog(NULL),
_image(NULL)
{
_threadController = new PX4FirmwareUpgradeThreadController(this);
Q_CHECK_PTR(_threadController);
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(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &FirmwareUpgradeController::_foundBoard);
connect(_threadController, &PX4FirmwareUpgradeThreadController::noBoardFound, this, &FirmwareUpgradeController::_noBoardFound);
connect(_threadController, &PX4FirmwareUpgradeThreadController::boardGone, this, &FirmwareUpgradeController::_boardGone);
connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBootloader, this, &FirmwareUpgradeController::_foundBootloader);
connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed, this, &FirmwareUpgradeController::_bootloaderSyncFailed);
connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error);
connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
connect(_threadController, &PX4FirmwareUpgradeThreadController::status, this, &FirmwareUpgradeController::_status);
connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseStarted, this, &FirmwareUpgradeController::_eraseStarted);
connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseComplete, this, &FirmwareUpgradeController::_eraseComplete);
connect(_threadController, &PX4FirmwareUpgradeThreadController::flashComplete, this, &FirmwareUpgradeController::_flashComplete);
connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
connect(LinkManager::instance(), &LinkManager::linkDisconnected, this, &FirmwareUpgradeController::_linkDisconnected);
connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
}
/// @brief Cancels the current state and returns to the begin start
void FirmwareUpgradeController::_cancel(void)
void FirmwareUpgradeController::startBoardSearch(void)
{
// Bootloader may still still open, reboot to close and heopfully get back to FMU
_threadController->sendBootloaderReboot();
Q_ASSERT(_upgradeButton);
_upgradeButton->setEnabled(true);
_bootloaderFound = false;
_startFlashWhenBootloaderFound = false;
_threadController->startFindBoardLoop();
}
/// @brief Begins the process or searching for the board
void FirmwareUpgradeController::_findBoard(void)
void FirmwareUpgradeController::flash(FirmwareType_t firmwareType)
{
QString msg("Plug your board into USB now. Press Ok when board is plugged in.");
_appendStatusLog(msg);
emit showMessage("Firmware Upgrade", msg);
_searchingForBoard = true;
_threadController->findBoard(_findBoardTimeoutMsec);
if (_bootloaderFound) {
_getFirmwareFile(firmwareType);
} else {
// We haven't found the bootloader yet. Need to wait until then to flash
_startFlashWhenBootloaderFound = true;
_startFlashWhenBootloaderFoundFirmwareType = firmwareType;
}
}
/// @brief Called when board has been found by the findBoard process
void FirmwareUpgradeController::_foundBoard(bool firstTry, const QString portName, QString portDescription)
void FirmwareUpgradeController::cancel(void)
{
if (firstTry) {
// Board is still plugged
_cancel();
emit showMessage("Board plugged in",
"Please unplug your board before beginning the Firmware Upgrade process. "
"Click Upgrade again once the board is unplugged.");
} else {
_portName = portName;
_portDescription = portDescription;
_eraseTimer.stop();
_threadController->cancel();
}
_appendStatusLog(tr("Board found:"));
_appendStatusLog(tr(" Port: %1").arg(_portName));
_appendStatusLog(tr(" Description: %1").arg(_portName));
_findBootloader();
void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int type)
{
_foundBoardInfo = info;
switch (type) {
case FoundBoardPX4FMUV1:
_foundBoardType = "PX4 FMU V1";
_startFlashWhenBootloaderFound = false;
break;
case FoundBoardPX4FMUV2:
_foundBoardType = "Pixhawk";
_startFlashWhenBootloaderFound = false;
break;
case FoundBoardPX4Flow:
case FoundBoard3drRadio:
_foundBoardType = type == FoundBoardPX4Flow ? "PX4 Flow" : "3DR Radio";
if (!firstAttempt) {
// PX4 Flow and Radio always flash stable firmware, so we can start right away without
// any further user input.
_startFlashWhenBootloaderFound = true;
_startFlashWhenBootloaderFoundFirmwareType = PX4StableFirmware;
}
break;
}
qCDebug(FirmwareUpgradeLog) << _foundBoardType;
emit boardFound();
}
/// @brief Begins the findBootloader process to connect to the bootloader
void FirmwareUpgradeController::_findBootloader(void)
void FirmwareUpgradeController::_noBoardFound(void)
{
_appendStatusLog(tr("Attemping to communicate with bootloader..."));
_searchingForBoard = false;
_threadController->findBootloader(_portName, _findBootloaderTimeoutMsec);
emit noBoardFound();
}
void FirmwareUpgradeController::_boardGone(void)
{
emit boardGone();
}
/// @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)
{
_bootloaderFound = true;
_bootloaderVersion = bootloaderVersion;
_boardID = boardID;
_boardFlashSize = flashSize;
_bootloaderBoardID = boardID;
_bootloaderBoardFlashSize = 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));
_appendStatusLog("Connected to bootloader:");
_appendStatusLog(QString(" Version: %1").arg(_bootloaderVersion));
_appendStatusLog(QString(" Board ID: %1").arg(_bootloaderBoardID));
_appendStatusLog(QString(" Flash size: %1").arg(_bootloaderBoardFlashSize));
_getFirmwareFile();
if (_startFlashWhenBootloaderFound) {
flash(_startFlashWhenBootloaderFoundFirmwareType);
}
}
/// @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.");
}
_cancel();
emit showMessage("Error", msg);
_errorCancel("Unable to sync with bootloader.");
}
/// @brief Prompts the user to select a firmware file if needed and moves the state machine to the next state.
void FirmwareUpgradeController::_getFirmwareFile(void)
void FirmwareUpgradeController::_getFirmwareFile(FirmwareType_t firmwareType)
{
static const char* rgPX4FMUV1Firmware[3] =
{
"http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v1_default.px4",
"http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v1_default.px4",
"http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v1_default.px4"
static DownloadLocationByFirmwareType_t rgPX4FMUV2Firmware[] = {
{ PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v2_default.px4" },
{ PX4BetaFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v2_default.px4" },
{ PX4DeveloperFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v2_default.px4"},
{ ApmArduCopterQuadFirmware, "http://firmware.diydrones.com/Copter/stable/PX4-quad/ArduCopter-v2.px4" },
{ ApmArduCopterX8Firmware, "http://firmware.diydrones.com/Copter/stable/PX4-octa-quad/ArduCopter-v2.px4" },
{ ApmArduCopterHexaFirmware, "http://firmware.diydrones.com/Copter/stable/PX4-hexa/ArduCopter-v2.px4" },
{ ApmArduCopterOctoFirmware, "http://firmware.diydrones.com/Copter/stable/PX4-octa/ArduCopter-v2.px4" },
{ ApmArduCopterYFirmware, "http://firmware.diydrones.com/Copter/stable/PX4-tri/ArduCopter-v2.px4" },
{ ApmArduCopterY6Firmware, "http://firmware.diydrones.com/Copter/stable/PX4-y6/ArduCopter-v2.px4" },
{ ApmArduCopterHeliFirmware, "http://firmware.diydrones.com/Copter/stable/PX4-heli/ArduCopter-v2.px4" },
{ ApmArduPlaneFirmware, "http://firmware.diydrones.com/Plane/stable/PX4/ArduPlane-v2.px4" },
{ ApmRoverFirmware, "http://firmware.diydrones.com/Plane/stable/PX4/APMrover2-v2.px4" },
};
static const size_t crgPX4FMUV2Firmware = sizeof(rgPX4FMUV2Firmware) / sizeof(rgPX4FMUV2Firmware[0]);
static const char* rgPX4FMUV2Firmware[3] =
{
"http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v2_default.px4",
"http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v2_default.px4",
"http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v2_default.px4"
static const DownloadLocationByFirmwareType_t rgAeroCoreFirmware[] = {
{ PX4StableFirmware, "http://s3-us-west-2.amazonaws.com/gumstix-aerocore/PX4/stable/aerocore_default.px4" },
{ PX4BetaFirmware, "http://s3-us-west-2.amazonaws.com/gumstix-aerocore/PX4/beta/aerocore_default.px4" },
{ PX4DeveloperFirmware, "http://s3-us-west-2.amazonaws.com/gumstix-aerocore/PX4/master/aerocore_default.px4" },
};
static const char* rgAeroCoreFirmware[3] =
{
"http://gumstix-aerocore.s3.amazonaws.com/PX4/stable/aerocore_default.px4",
"http://gumstix-aerocore.s3.amazonaws.com/PX4/beta/aerocore_default.px4",
"http://gumstix-aerocore.s3.amazonaws.com/PX4/master/aerocore_default.px4"
static const size_t crgAeroCoreFirmware = sizeof(rgAeroCoreFirmware) / sizeof(rgAeroCoreFirmware[0]);
static const DownloadLocationByFirmwareType_t rgPX4FMUV1Firmware[] = {
{ PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v1_default.px4" },
{ PX4BetaFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v1_default.px4" },
{ PX4DeveloperFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v1_default.px4" },
};
static const char* rgPX4FlowFirmware[3] =
{
"http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4",
"http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4",
"http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4"
static const size_t crgPX4FMUV1Firmware = sizeof(rgPX4FMUV1Firmware) / sizeof(rgPX4FMUV1Firmware[0]);
static const DownloadLocationByFirmwareType_t rgPX4FlowFirmware[] = {
{ PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4" },
};
static const size_t crgPX4FlowFirmware = sizeof(rgPX4FlowFirmware) / sizeof(rgPX4FlowFirmware[0]);
Q_ASSERT(sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FMUV2Firmware) && sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FlowFirmware));
static const DownloadLocationByFirmwareType_t rg3DRRadioFirmware[] = {
{ PX4StableFirmware, "http://firmware.diydrones.com/SiK/stable/radio~hm_trp.ihx" },
};
static const size_t crg3DRRadioFirmware = sizeof(rg3DRRadioFirmware) / sizeof(rg3DRRadioFirmware[0]);
// Select the firmware set based on board type
const char** prgFirmware;
switch (_boardID) {
case _boardIDPX4FMUV1:
const DownloadLocationByFirmwareType_t* prgFirmware;
size_t crgFirmware;
switch (_bootloaderBoardID) {
case Bootloader::boardIDPX4FMUV1:
prgFirmware = rgPX4FMUV1Firmware;
crgFirmware = crgPX4FMUV1Firmware;
break;
case _boardIDPX4Flow:
case Bootloader::boardIDPX4Flow:
prgFirmware = rgPX4FlowFirmware;
crgFirmware = crgPX4FlowFirmware;
break;
case _boardIDPX4FMUV2:
case Bootloader::boardIDPX4FMUV2:
prgFirmware = rgPX4FMUV2Firmware;
crgFirmware = crgPX4FMUV2Firmware;
break;
case _boardIDAeroCore:
case Bootloader::boardIDAeroCore:
prgFirmware = rgAeroCoreFirmware;
crgFirmware = crgAeroCoreFirmware;
break;
case Bootloader::boardID3DRRadio:
prgFirmware = rg3DRRadioFirmware;
crgFirmware = crg3DRRadioFirmware;
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 (prgFirmware == NULL && firmwareType != PX4CustomFirmware) {
_errorCancel("Attempting to flash an unknown board type, you must select 'Custom firmware file'");
return;
}
if (_firmwareType == CustomFirmware) {
if (firmwareType == PX4CustomFirmware) {
_firmwareFilename = QGCFileDialog::getOpenFileName(NULL, // Parent to main window
tr("Select Firmware File"), // Dialog Caption
"Select Firmware File", // Dialog Caption
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), // Initial directory
tr("Firmware Files (*.px4 *.bin)")); // File filter
"Firmware Files (*.px4 *.bin *.ihx)"); // File filter
} else {
_firmwareFilename = prgFirmware[_firmwareType];
bool found = false;
for (size_t i=0; i<crgFirmware; i++) {
if (prgFirmware->firmwareType == firmwareType) {
found = true;
break;
}
prgFirmware++;
}
if (found) {
_firmwareFilename = prgFirmware->downloadLocation;
} else {
_errorCancel("Unable to find specified firmware download location");
return;
}
}
if (_firmwareFilename.isEmpty()) {
_cancel();
_errorCancel("No firmware file selected");
} else {
_downloadFirmware();
}
......@@ -234,8 +267,8 @@ void FirmwareUpgradeController::_downloadFirmware(void)
{
Q_ASSERT(!_firmwareFilename.isEmpty());
_appendStatusLog(tr("Downloading firmware..."));
_appendStatusLog(tr(" From: %1").arg(_firmwareFilename));
_appendStatusLog("Downloading firmware...");
_appendStatusLog(QString(" From: %1").arg(_firmwareFilename));
// Split out filename from path
QString firmwareFilename = QFileInfo(_firmwareFilename).fileName();
......@@ -246,13 +279,12 @@ void FirmwareUpgradeController::_downloadFirmware(void)
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();
_errorCancel("Unabled to find writable download location. Tried downloads and temp directory.");
return;
}
}
Q_ASSERT(!downloadFile.isEmpty());
downloadFile += "/" + firmwareFilename;
downloadFile += "/" + firmwareFilename;
QUrl firmwareUrl;
if (_firmwareFilename.startsWith("http:")) {
......@@ -290,7 +322,7 @@ void FirmwareUpgradeController::_downloadProgress(qint64 curr, qint64 total)
/// @brief Called when the firmware download completes.
void FirmwareUpgradeController::_downloadFinished(void)
{
_appendStatusLog(tr("Download complete"));
_appendStatusLog("Download complete");
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
Q_ASSERT(reply);
......@@ -313,286 +345,72 @@ void FirmwareUpgradeController::_downloadFinished(void)
// 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();
_errorCancel(QString("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString()));
return;
}
file.write(reply->readAll());
file.close();
FirmwareImage* image = new FirmwareImage(this);
connect(image, &FirmwareImage::statusMessage, this, &FirmwareUpgradeController::_status);
connect(image, &FirmwareImage::errorMessage, this, &FirmwareUpgradeController::_error);
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<sizeof(rgJsonKeys)/sizeof(rgJsonKeys[0]); i++) {
if (!px4Json.contains(rgJsonKeys[i])) {
_appendStatusLog(tr("Incorrectly formatted firmware file. No %1 key.").arg(rgJsonKeys[i]));
_cancel();
return;
}
}
uint32_t firmwareBoardID = (uint32_t)px4Json.value(QString("board_id")).toInt();
if (firmwareBoardID != _boardID) {
_appendStatusLog(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardID).arg(_boardID));
_cancel();
return;
}
// Decompress the parameter xml and save to file
QByteArray decompressedBytes;
bool success = _decompressJsonValue(px4Json, // JSON object
bytes, // Raw bytes of JSON document
"parameter_xml_size", // key which holds byte size
"parameter_xml", // key which holds compress bytes
decompressedBytes); // Returned decompressed bytes
if (success) {
QSettings settings;
QDir parameterDir = QFileInfo(settings.fileName()).dir();
QString parameterFilename = parameterDir.filePath("PX4ParameterFactMetaData.xml");
qDebug() << parameterFilename;
QFile parameterFile(parameterFilename);
if (parameterFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qint64 bytesWritten = parameterFile.write(decompressedBytes);
if (bytesWritten != decompressedBytes.count()) {
_appendStatusLog(tr("Write failed for parameter meta data file, error: %1").arg(parameterFile.errorString()));
parameterFile.close();
QFile::remove(parameterFilename);
} else {
parameterFile.close();
}
} else {
_appendStatusLog(tr("Unable to open parameter meta data file %1 for writing, error: %2").arg(parameterFilename).arg(parameterFile.errorString()));
}
}
// FIXME: Save NYI
// Decompress the image and save to file
_imageSize = px4Json.value(QString("image_size")).toInt();
success = _decompressJsonValue(px4Json, // JSON object
bytes, // Raw bytes of JSON document
"image_size", // key which holds byte size
"image", // key which holds compress bytes
decompressedBytes); // Returned decompressed bytes
if (!success) {
_cancel();
return;
}
// Pad image to 4-byte boundary
while ((decompressedBytes.count() % 4) != 0) {
decompressedBytes.append(static_cast<char>(static_cast<unsigned char>(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(decompressedBytes);
if (bytesWritten != decompressedBytes.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;
} else if (downloadFilename.toLower().contains("aerocore")) {
firmwareBoardID = _boardIDAeroCore;
}
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 (!image->load(downloadFilename, _bootloaderBoardID)) {
_errorCancel("Image load failed");
return;
}
if (_imageSize > _boardFlashSize) {
_appendStatusLog(tr("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_boardFlashSize));
_cancel();
// We can't proceed unless we have the bootloader
if (!_bootloaderFound) {
_errorCancel(QString("Bootloader not found").arg(_imageSize).arg(_bootloaderBoardFlashSize));
return;
}
_erase();
}
/// Decompress a set of bytes stored in a Json document.
bool FirmwareUpgradeController::_decompressJsonValue(const QJsonObject& jsonObject, ///< JSON object
const QByteArray& jsonDocBytes, ///< Raw bytes of JSON document
const QString& sizeKey, ///< key which holds byte size
const QString& bytesKey, ///< key which holds compress bytes
QByteArray& decompressedBytes) ///< Returned decompressed bytes
{
// Validate decompressed size key
if (!jsonObject.contains(sizeKey)) {
_appendStatusLog(QString("Firmware file missing %1 key").arg(sizeKey));
return false;
}
int decompressedSize = jsonObject.value(QString(sizeKey)).toInt();
if (decompressedSize == 0) {
_appendStatusLog(QString("Firmware file has invalid decompressed size for %1").arg(sizeKey));
return false;
}
// 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 parts = QString(jsonDocBytes).split(QString("\"%1\": \"").arg(bytesKey));
if (parts.count() == 1) {
_appendStatusLog(QString("Could not find compressed bytes for %1 in Firmware file").arg(bytesKey));
return false;
}
parts = parts.last().split("\"");
if (parts.count() == 1) {
_appendStatusLog(QString("Incorrectly formed compressed bytes section for %1 in Firmware file").arg(bytesKey));
return false;
}
// Store decompressed size as first four bytes. This is required by qUncompress routine.
QByteArray raw;
raw.append((unsigned char)((decompressedSize >> 24) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 16) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 8) & 0xFF));
raw.append((unsigned char)((decompressedSize >> 0) & 0xFF));
QByteArray raw64 = parts.first().toUtf8();
raw.append(QByteArray::fromBase64(raw64));
decompressedBytes = qUncompress(raw);
if (decompressedBytes.count() == 0) {
_appendStatusLog(QString("Firmware file has 0 length %1").arg(bytesKey));
return false;
}
if (decompressedBytes.count() != decompressedSize) {
_appendStatusLog(QString("Size for decompressed %1 does not match stored size: Expected(%1) Actual(%2)").arg(decompressedSize).arg(decompressedBytes.count()));
return false;
if (_bootloaderBoardFlashSize != 0 && _imageSize > _bootloaderBoardFlashSize) {
_errorCancel(QString("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_bootloaderBoardFlashSize));
return;
}
_appendStatusLog(QString("Succesfully decompressed %1").arg(bytesKey));
return true;
_threadController->flash(image);
}
/// @brief Called when an error occurs during download
void FirmwareUpgradeController::_downloadError(QNetworkReply::NetworkError code)
{
QString errorMsg;
if (code == QNetworkReply::OperationCanceledError) {
_appendStatusLog(tr("Download cancelled"));
errorMsg = "Download cancelled";
} else {
_appendStatusLog(tr("Error during download. Error: %1").arg(code));
errorMsg = QString("Error during download. Error: %1").arg(code);
}
_cancel();
_errorCancel(errorMsg);
}
/// @brief Erase the board
void FirmwareUpgradeController::_erase(void)
/// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the
/// appropriate next step.
void FirmwareUpgradeController::_flashComplete(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);
delete _image;
_image = NULL;
// Erase command
_threadController->erase();
_appendStatusLog("Upgrade complete", true);
_appendStatusLog("------------------------------------------", false);
emit flashComplete();
}
/// @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)
void FirmwareUpgradeController::_error(const QString& errorString)
{
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);
}
delete _image;
_image = NULL;
_errorCancel(QString("Error: %1").arg(errorString));
}
/// @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)
void FirmwareUpgradeController::_status(const QString& statusString)
{
Q_UNUSED(command);
_appendStatusLog(tr("Error: %1").arg(errorString));
_cancel();
_appendStatusLog(statusString);
}
/// @brief Updates the progress bar from long running bootloader commands
......@@ -604,13 +422,6 @@ void FirmwareUpgradeController::_updateProgress(int curr, int 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)
{
......@@ -618,33 +429,54 @@ void FirmwareUpgradeController::_eraseProgressTick(void)
_progressBar->setProperty("value", (float)(_eraseTickCount*_eraseTickMsec) / (float)_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)
void FirmwareUpgradeController::_appendStatusLog(const QString& text, bool critical)
{
Q_ASSERT(_statusLog);
QVariant returnedValue;
QVariant varText = text;
QVariant varText;
if (critical) {
varText = QString("<font color=\"yellow\">%1</font>").arg(text);
} else {
varText = text;
}
QMetaObject::invokeMethod(_statusLog,
"append",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, varText));
}
bool FirmwareUpgradeController::activeQGCConnections(void)
bool FirmwareUpgradeController::qgcConnections(void)
{
return LinkManager::instance()->anyConnectedLinks();
}
bool FirmwareUpgradeController::pluggedInBoard(void)
void FirmwareUpgradeController::_linkDisconnected(LinkInterface* link)
{
Q_UNUSED(link);
emit qgcConnectionsChanged(qgcConnections());
}
void FirmwareUpgradeController::_errorCancel(const QString& msg)
{
_appendStatusLog(msg, false);
_appendStatusLog("Upgrade cancelled", true);
_appendStatusLog("------------------------------------------", false);
emit error();
cancel();
}
void FirmwareUpgradeController::_eraseStarted(void)
{
// We set up our own progress bar for erase since the erase command does not provide one
_eraseTickCount = 0;
_eraseTimer.start(_eraseTickMsec);
}
void FirmwareUpgradeController::_eraseComplete(void)
{
return _threadController->pluggedInBoard();
_eraseTimer.stop();
}
......@@ -28,6 +28,8 @@
#define FirmwareUpgradeController_H
#include "PX4FirmwareUpgradeThread.h"
#include "LinkManager.h"
#include "FirmwareImage.h"
#include <QObject>
#include <QUrl>
......@@ -51,37 +53,46 @@ public:
/// Supported firmware types. If you modify these you will need to update the qml file as well.
typedef enum {
StableFirmware,
BetaFirmware,
DeveloperFirmware,
CustomFirmware
PX4StableFirmware,
PX4BetaFirmware,
PX4DeveloperFirmware,
PX4CustomFirmware,
ApmArduCopterQuadFirmware,
ApmArduCopterX8Firmware,
ApmArduCopterHexaFirmware,
ApmArduCopterOctoFirmware,
ApmArduCopterYFirmware,
ApmArduCopterY6Firmware,
ApmArduCopterHeliFirmware,
ApmArduPlaneFirmware,
ApmRoverFirmware,
} FirmwareType_t;
Q_ENUMS(FirmwareType_t)
/// Firmare type to load
Q_PROPERTY(FirmwareType_t firmwareType READ firmwareType WRITE setFirmwareType)
Q_PROPERTY(QString boardPort READ boardPort NOTIFY boardFound)
Q_PROPERTY(QString boardDescription READ boardDescription NOTIFY boardFound)
Q_PROPERTY(QString boardType MEMBER _foundBoardType NOTIFY boardFound)
/// 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)
/// Progress bar for you know what
Q_PROPERTY(QQuickItem* progressBar READ progressBar WRITE setProgressBar)
/// Returns true if there are active QGC connections
Q_PROPERTY(bool qgcConnections READ qgcConnections NOTIFY qgcConnectionsChanged)
Q_INVOKABLE bool activeQGCConnections(void);
Q_INVOKABLE bool pluggedInBoard(void);
/// Starts searching for boards on the background thread
Q_INVOKABLE void startBoardSearch(void);
/// Begins the firware upgrade process
Q_INVOKABLE void doFirmwareUpgrade(void);
FirmwareType_t firmwareType(void) { return _firmwareType; }
void setFirmwareType(FirmwareType_t firmwareType) { _firmwareType = firmwareType; }
/// Cancels whatever state the upgrade worker thread is in
Q_INVOKABLE void cancel(void);
QQuickItem* upgradeButton(void) { return _upgradeButton; }
void setUpgradeButton(QQuickItem* upgradeButton) { _upgradeButton = upgradeButton; }
/// Called when the firmware type has been selected by the user to continue the flash process.
Q_INVOKABLE void flash(FirmwareType_t firmwareType);
// Property accessors
QQuickItem* progressBar(void) { return _progressBar; }
void setProgressBar(QQuickItem* progressBar) { _progressBar = progressBar; }
......@@ -89,48 +100,61 @@ public:
QQuickItem* statusLog(void) { return _statusLog; }
void setStatusLog(QQuickItem* statusLog) { _statusLog = statusLog; }
bool qgcConnections(void);
QString boardPort(void) { return _foundBoardInfo.portName(); }
QString boardDescription(void) { return _foundBoardInfo.description(); }
signals:
void showMessage(const QString& title, const QString& message);
void boardFound(void);
void noBoardFound(void);
void boardGone(void);
void flashComplete(void);
void flashCancelled(void);
void qgcConnectionsChanged(bool connections);
void error(void);
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 _foundBoard(bool firstAttempt, const QSerialPortInfo& portInfo, int type);
void _noBoardFound(void);
void _boardGone();
void _foundBootloader(int bootloaderVersion, int boardID, int flashSize);
void _error(const int command, const QString errorString);
void _error(const QString& errorString);
void _status(const QString& statusString);
void _bootloaderSyncFailed(void);
void _findTimeout(void);
void _complete(const int command);
void _flashComplete(void);
void _updateProgress(int curr, int total);
void _restart(void);
void _eraseStarted(void);
void _eraseComplete(void);
void _eraseProgressTick(void);
void _linkDisconnected(LinkInterface* link);
private:
void _findBoard(void);
void _findBootloader(void);
void _cancel(void);
void _getFirmwareFile(void);
void _getFirmwareFile(FirmwareType_t firmwareType);
void _downloadFirmware(void);
void _erase(void);
void _appendStatusLog(const QString& text);
bool _decompressJsonValue(const QJsonObject& jsonObject,
const QByteArray& jsonDocBytes,
const QString& sizeKey,
const QString& bytesKey,
QByteArray& decompressedBytes);
void _appendStatusLog(const QString& text, bool critical = false);
void _errorCancel(const QString& msg);
typedef struct {
FirmwareType_t firmwareType;
const char* downloadLocation;
} DownloadLocationByFirmwareType_t;
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
static const int _boardIDAeroCore = 98; ///< Board ID for Gumstix AeroCore board
uint32_t _boardID; ///< Board ID
uint32_t _boardFlashSize; ///< Flash size in bytes of board
/// Information which comes back from the bootloader
bool _bootloaderFound; ///< true: we have received the foundBootloader signals
uint32_t _bootloaderVersion; ///< Bootloader version
uint32_t _bootloaderBoardID; ///< Board ID
uint32_t _bootloaderBoardFlashSize; ///< Flash size in bytes of board
bool _startFlashWhenBootloaderFound;
FirmwareType_t _startFlashWhenBootloaderFoundFirmwareType;
uint32_t _imageSize; ///< Image size of firmware being flashed
QPixmap _boardIcon; ///< Icon used to display image of board
......@@ -151,12 +175,15 @@ private:
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
QQuickItem* _progressBar;
bool _searchingForBoard; ///< true: searching for board, false: search for bootloader
QSerialPortInfo _foundBoardInfo;
QString _foundBoardType;
FirmwareImage* _image;
};
#endif
......@@ -26,20 +26,30 @@
/// @author Don Gagne <don@thegagnes.com>
#include "PX4FirmwareUpgradeThread.h"
#include "PX4Bootloader.h"
#include "Bootloader.h"
#include "QGCLoggingCategory.h"
#include "QGC.h"
#include <QTimer>
#include <QSerialPortInfo>
#include <QDebug>
#include <QserialPort>
PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(QObject* parent) :
QObject(parent),
PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(PX4FirmwareUpgradeThreadController* controller) :
_controller(controller),
_bootloader(NULL),
_bootloaderPort(NULL),
_timerTimeout(NULL),
_timerRetry(NULL)
_timerRetry(NULL),
_foundBoard(false),
_findBoardFirstAttempt(true)
{
Q_ASSERT(_controller);
connect(_controller, &PX4FirmwareUpgradeThreadController::_initThreadWorker, this, &PX4FirmwareUpgradeThreadWorker::_init);
connect(_controller, &PX4FirmwareUpgradeThreadController::_startFindBoardLoopOnThread, this, &PX4FirmwareUpgradeThreadWorker::_startFindBoardLoop);
connect(_controller, &PX4FirmwareUpgradeThreadController::_flashOnThread, this, &PX4FirmwareUpgradeThreadWorker::_flash);
connect(_controller, &PX4FirmwareUpgradeThreadController::_rebootOnThread, this, &PX4FirmwareUpgradeThreadWorker::_reboot);
connect(_controller, &PX4FirmwareUpgradeThreadController::_cancel, this, &PX4FirmwareUpgradeThreadWorker::_cancel);
}
PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker()
......@@ -52,77 +62,112 @@ PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker()
/// @brief Initializes the PX4FirmwareUpgradeThreadWorker with the various child objects which must be created
/// on the worker thread.
void PX4FirmwareUpgradeThreadWorker::init(void)
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);
connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce);
Q_ASSERT(_bootloader == NULL);
_bootloader = new PX4Bootloader(this);
connect(_bootloader, &PX4Bootloader::updateProgramProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgramProgress);
_bootloader = new Bootloader(this);
connect(_bootloader, &Bootloader::updateProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgress);
}
void PX4FirmwareUpgradeThreadWorker::findBoard(int msecTimeout)
void PX4FirmwareUpgradeThreadWorker::_cancel(void)
{
if (_bootloaderPort) {
_bootloaderPort->close();
_bootloaderPort->deleteLater();
_bootloaderPort = NULL;
}
}
void PX4FirmwareUpgradeThreadWorker::_startFindBoardLoop(void)
{
_foundBoard = false;
_findBoardFirstAttempt = true;
connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce);
_timerTimeout->start(msecTimeout);
_elapsed.start();
_findBoardOnce();
}
void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void)
{
qDebug() << "_findBoardOnce";
qCDebug(FirmwareUpgradeLog) << "_findBoardOnce";
QString portName;
QString portDescription;
QSerialPortInfo portInfo;
PX4FirmwareUpgradeFoundBoardType_t boardType;
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;
if (_findBoardFromPorts(portInfo, boardType)) {
if (!_foundBoard) {
_foundBoard = true;
_foundBoardPortInfo = portInfo;
emit foundBoard(_findBoardFirstAttempt, portInfo, boardType);
if (!_findBoardFirstAttempt) {
if (boardType == FoundBoard3drRadio) {
_3drRadioForceBootloader(portInfo);
return;
} else {
_findBootloader(portInfo, false /* radio mode */, true /* errorOnNotFound */);
return;
}
}
}
} else {
if (_foundBoard) {
_foundBoard = false;
qCDebug(FirmwareUpgradeLog) << "Board gone";
emit boardGone();
} else if (_findBoardFirstAttempt) {
emit noBoardFound();
}
}
_findBoardFirstAttempt = false;
emit updateProgress(_elapsed.elapsed(), _timerTimeout->interval());
_timerRetry->start();
}
bool PX4FirmwareUpgradeThreadController::pluggedInBoard(void)
bool PX4FirmwareUpgradeThreadWorker::_findBoardFromPorts(QSerialPortInfo& portInfo, PX4FirmwareUpgradeFoundBoardType_t& type)
{
qDebug() << "pluggedInBoard";
QString portName;
QString portDescription;
bool found = false;
foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) {
if (!info.portName().isEmpty() && (info.description().contains("PX4") || info.vendorIdentifier() == 9900 /* 3DR */)) {
#if 0
qCDebug(FirmwareUpgradeLog) << "Serial Port --------------";
qCDebug(FirmwareUpgradeLog) << "\tport name:" << info.portName();
qCDebug(FirmwareUpgradeLog) << "\tdescription:" << info.description();
qCDebug(FirmwareUpgradeLog) << "\tsystem location:" << info.systemLocation();
qCDebug(FirmwareUpgradeLog) << "\tvendor ID:" << info.vendorIdentifier();
qCDebug(FirmwareUpgradeLog) << "\tproduct ID:" << info.productIdentifier();
#endif
if (!info.portName().isEmpty()) {
if (info.vendorIdentifier() == _px4VendorId) {
if (info.productIdentifier() == _pixhawkFMUV2ProductId) {
qCDebug(FirmwareUpgradeLog) << "Found PX4 FMU V2";
type = FoundBoardPX4FMUV2;
found = true;
} else if (info.productIdentifier() == _pixhawkFMUV1ProductId) {
qCDebug(FirmwareUpgradeLog) << "Found PX4 FMU V1";
type = FoundBoardPX4FMUV2;
found = true;
} else if (info.productIdentifier() == _flowProductId) {
qCDebug(FirmwareUpgradeLog) << "Found PX4 Flow";
type = FoundBoardPX4Flow;
found = true;
}
} else if (info.vendorIdentifier() == _3drRadioVendorId && info.productIdentifier() == _3drRadioProductId) {
qCDebug(FirmwareUpgradeLog) << "Found 3DR Radio";
type = FoundBoard3drRadio;
found = true;
}
}
if (found) {
portInfo = info;
return true;
}
}
......@@ -130,144 +175,197 @@ bool PX4FirmwareUpgradeThreadController::pluggedInBoard(void)
return false;
}
void PX4FirmwareUpgradeThreadWorker::findBootloader(const QString portName, int msecTimeout)
void PX4FirmwareUpgradeThreadWorker::_3drRadioForceBootloader(const QSerialPortInfo& portInfo)
{
Q_UNUSED(msecTimeout);
// First make sure we can't get the bootloader
if (_findBootloader(portInfo, true /* radio Mode */, false /* errorOnNotFound */)) {
return;
}
// Couldn't find the bootloader. We'll need to reboot the radio into bootloader.
QSerialPort port(portInfo);
// Once the port shows up, we only try to connect to the bootloader a single time
_portName = portName;
_findBootloaderOnce();
port.setBaudRate(QSerialPort::Baud57600);
emit status("Putting radio into command mode");
// Wait a little while for the USB port to initialize. 3DR Radio boot is really slow.
for (int i=0; i<12; i++) {
if (port.open(QIODevice::ReadWrite)) {
break;
} else {
QGC::SLEEP::msleep(250);
}
}
if (!port.isOpen()) {
emit error(QString("Unable to open port: %1 error: %2").arg(portInfo.systemLocation()).arg(port.errorString()));
return;
}
// Put radio into command mode
port.write("+++", 3);
if (!port.waitForReadyRead(1500)) {
emit error("Unable to put radio into command mode");
return;
}
QByteArray bytes = port.readAll();
if (!bytes.contains("OK")) {
emit error("Unable to put radio into command mode");
return;
}
emit status("Rebooting radio to bootloader");
port.write("AT&UPDATE\r\n");
if (!port.waitForBytesWritten(1500)) {
emit error("Unable to reboot radio");
return;
}
QGC::SLEEP::msleep(2000);
port.close();
// The bootloader should be waiting for us now
_findBootloader(portInfo, true /* radio mode */, true /* errorOnNotFound */);
}
void PX4FirmwareUpgradeThreadWorker::_findBootloaderOnce(void)
bool PX4FirmwareUpgradeThreadWorker::_findBootloader(const QSerialPortInfo& portInfo, bool radioMode, bool errorOnNotFound)
{
qDebug() << "_findBootloaderOnce";
qCDebug(FirmwareUpgradeLog) << "_findBootloader";
uint32_t bootloaderVersion, boardID, flashSize;
uint32_t bootloaderVersion = 0;
uint32_t boardID;
uint32_t flashSize = 0;
_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;
}
// Wait a little while for the USB port to initialize.
for (int i=0; i<10; i++) {
if (_bootloader->open(_bootloaderPort, portInfo.systemLocation())) {
break;
} else {
QGC::SLEEP::msleep(100);
}
}
QGC::SLEEP::msleep(2000);
if (_bootloader->sync(_bootloaderPort)) {
bool success;
if (radioMode) {
success = _bootloader->get3DRRadioBoardId(_bootloaderPort, boardID);
} else {
success = _bootloader->getPX4BoardInfo(_bootloaderPort, bootloaderVersion, boardID, flashSize);
}
if (success) {
qCDebug(FirmwareUpgradeLog) << "Found bootloader";
emit foundBootloader(bootloaderVersion, boardID, flashSize);
return true;
}
}
_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();
qCDebug(FirmwareUpgradeLog) << "Bootloader error:" << _bootloader->errorString();
if (errorOnNotFound) {
emit error(_bootloader->errorString());
}
return false;
}
void PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot(void)
void PX4FirmwareUpgradeThreadWorker::_reboot(void)
{
if (_bootloaderPort) {
if (_bootloaderPort->isOpen()) {
_bootloader->sendBootloaderReboot(_bootloaderPort);
_bootloader->reboot(_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)
void PX4FirmwareUpgradeThreadWorker::_flash(void)
{
qDebug() << "Verify";
if (!_bootloader->verify(_bootloaderPort, firmwareFilename)) {
qDebug() << "Verify failed:" << _bootloader->errorString();
emit error(commandVerify, _bootloader->errorString());
} else {
qDebug() << "Verify complete";
emit complete(commandVerify);
qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadWorker::_flash";
if (_erase()) {
emit status("Programming new version...");
if (_bootloader->program(_bootloaderPort, _controller->image())) {
qCDebug(FirmwareUpgradeLog) << "Program complete";
emit status("Program complete");
} else {
_bootloaderPort->deleteLater();
_bootloaderPort = NULL;
qCDebug(FirmwareUpgradeLog) << "Program failed:" << _bootloader->errorString();
emit error(_bootloader->errorString());
}
emit status("Verifying program...");
if (_bootloader->verify(_bootloaderPort, _controller->image())) {
qCDebug(FirmwareUpgradeLog) << "Verify complete";
emit status("Verify complete");
} else {
qCDebug(FirmwareUpgradeLog) << "Verify failed:" << _bootloader->errorString();
emit error(_bootloader->errorString());
}
}
_bootloaderPort->deleteLater();
_bootloaderPort = NULL;
emit _reboot();
emit flashComplete();
}
void PX4FirmwareUpgradeThreadWorker::erase(void)
bool PX4FirmwareUpgradeThreadWorker::_erase(void)
{
qDebug() << "Erase";
if (!_bootloader->erase(_bootloaderPort)) {
_bootloaderPort->deleteLater();
_bootloaderPort = NULL;
qDebug() << "Erase failed:" << _bootloader->errorString();
emit error(commandErase, _bootloader->errorString());
qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadWorker::_erase";
emit eraseStarted();
emit status("Erasing previous program...");
if (_bootloader->erase(_bootloaderPort)) {
qCDebug(FirmwareUpgradeLog) << "Erase complete";
emit status("Erase complete");
emit eraseComplete();
return true;
} else {
qDebug() << "Erase complete";
emit complete(commandErase);
qCDebug(FirmwareUpgradeLog) << "Erase failed:" << _bootloader->errorString();
emit error(_bootloader->errorString());
return false;
}
}
PX4FirmwareUpgradeThreadController::PX4FirmwareUpgradeThreadController(QObject* parent) :
QObject(parent)
{
_worker = new PX4FirmwareUpgradeThreadWorker();
_worker = new PX4FirmwareUpgradeThreadWorker(this);
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::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoard, this, &PX4FirmwareUpgradeThreadController::_foundBoard);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::noBoardFound, this, &PX4FirmwareUpgradeThreadController::_noBoardFound);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::boardGone, this, &PX4FirmwareUpgradeThreadController::_boardGone);
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(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::status, this, &PX4FirmwareUpgradeThreadController::_status);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::eraseStarted, this, &PX4FirmwareUpgradeThreadController::_eraseStarted);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::eraseComplete, this, &PX4FirmwareUpgradeThreadController::_eraseComplete);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::flashComplete, this, &PX4FirmwareUpgradeThreadController::_flashComplete);
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();
......@@ -277,36 +375,24 @@ PX4FirmwareUpgradeThreadController::~PX4FirmwareUpgradeThreadController()
{
_workerThread->quit();
_workerThread->wait();
delete _workerThread;
}
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)
void PX4FirmwareUpgradeThreadController::startFindBoardLoop(void)
{
emit foundBoard(firstTry, portName, portDescription);
qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadController::findBoard";
emit _startFindBoardLoopOnThread();
}
void PX4FirmwareUpgradeThreadController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize)
void PX4FirmwareUpgradeThreadController::cancel(void)
{
emit foundBootloader(bootloaderVersion, boardID, flashSize);
}
void PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed(void)
{
emit bootloaderSyncFailed();
qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadController::cancel";
emit _cancel();
}
void PX4FirmwareUpgradeThreadController::_findTimeout(void)
void PX4FirmwareUpgradeThreadController::flash(const FirmwareImage* image)
{
emit findTimeout();
_image = image;
emit _flashOnThread();
}
......@@ -28,16 +28,27 @@
#ifndef PX4FirmwareUpgradeThread_H
#define PX4FirmwareUpgradeThread_H
#include "Bootloader.h"
#include "FirmwareImage.h"
#include <QObject>
#include <QThread>
#include <QTimer>
#include <QTime>
#include <QSerialPortInfo>
#include "qextserialport.h"
#include <stdint.h>
#include "PX4Bootloader.h"
typedef enum {
FoundBoardPX4FMUV1,
FoundBoardPX4FMUV2,
FoundBoardPX4Flow,
FoundBoard3drRadio
} PX4FirmwareUpgradeFoundBoardType_t;
class PX4FirmwareUpgradeThreadController;
/// @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
......@@ -47,52 +58,59 @@ class PX4FirmwareUpgradeThreadWorker : public QObject
Q_OBJECT
public:
PX4FirmwareUpgradeThreadWorker(QObject* parent = NULL);
PX4FirmwareUpgradeThreadWorker(PX4FirmwareUpgradeThreadController* controller);
~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 updateProgress(int curr, int total);
void foundBoard(bool firstAttempt, const QSerialPortInfo& portInfo, int type);
void noBoardFound(void);
void boardGone(void);
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);
void error(const QString& errorString);
void status(const QString& statusText);
void eraseStarted(void);
void eraseComplete(void);
void flashComplete(void);
private slots:
void _init(void);
void _startFindBoardLoop(void);
void _reboot(void);
void _flash(void);
void _findBoardOnce(void);
void _findBootloaderOnce(void);
void _updateProgramProgress(int curr, int total) { emit updateProgress(curr, total); }
void _closeFind(void);
void _updateProgress(int curr, int total) { emit updateProgress(curr, total); }
void _cancel(void);
private:
PX4Bootloader* _bootloader;
bool _findBoardFromPorts(QSerialPortInfo& portInfo, PX4FirmwareUpgradeFoundBoardType_t& type);
bool _findBootloader(const QSerialPortInfo& portInfo, bool radioMode, bool errorOnNotFound);
void _3drRadioForceBootloader(const QSerialPortInfo& portInfo);
bool _erase(void);
PX4FirmwareUpgradeThreadController* _controller;
Bootloader* _bootloader;
QextSerialPort* _bootloaderPort;
QTimer* _timerTimeout;
QTimer* _timerRetry;
QTime _elapsed;
QString _portName;
static const int _retryTimeout = 1000;
bool _findBoardFirstAttempt;
bool _foundBoard; ///< true: board is currently connected
bool _findBoardFirstAttempt; ///< true: this is our first try looking for a board
QSerialPortInfo _foundBoardPortInfo; ///< port info for found board
// Serial port info for supported devices
static const int _px4VendorId = 9900;
static const int _pixhawkFMUV2ProductId = 17;
static const int _pixhawkFMUV1ProductId = 16;
static const int _flowProductId = 21;
static const int _3drRadioVendorId = 1027;
static const int _3drRadioProductId = 24597;
};
/// @brief Provides methods to interact with the bootloader. The commands themselves are signalled
......@@ -105,82 +123,74 @@ public:
PX4FirmwareUpgradeThreadController(QObject* parent = NULL);
~PX4FirmwareUpgradeThreadController(void);
/// Returns true is a board is currently connected via USB
bool pluggedInBoard(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 Begins the process of searching for a supported board connected to any serial port. This will
/// continue until cancelFind is called. Signals foundBoard and boardGone as boards come and go.
void startFindBoardLoop(void);
/// @brief Cancel an in progress findBoard or FindBootloader
void cancelFind(void) { emit _cancelFindOnThread(); }
void cancel(void);
/// @brief Sends a reboot command to the bootloader
void sendBootloaderReboot(void) { emit _sendBootloaderRebootOnThread(); }
void reboot(void) { emit _rebootOnThread(); }
/// @brief Flash the specified firmware onto the board
void program(const QString firmwareFilename) { emit _programOnThread(firmwareFilename); }
void flash(const FirmwareImage* image);
/// @brief Verify the board flash with respect to the specified firmware image
void verify(const QString firmwareFilename) { emit _verifyOnThread(firmwareFilename); }
const FirmwareImage* image(void) { return _image; }
/// @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 find board process when it finds a board.
void foundBoard(bool firstAttempt, const QSerialPortInfo &portInfo, int type);
void noBoardFound(void);
/// @brief Emitted by the find board process when a board it previously reported as found disappears.
void boardGone(void);
/// @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);
void error(const QString& errorString);
void status(const QString& status);
/// @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);
void eraseStarted(void);
void eraseComplete(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);
void flashComplete(void);
/// @brief Signalled to update progress for long running bootloader commands
void updateProgress(int curr, int total);
// Internal signals to communicate with thread worker
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);
void _startFindBoardLoopOnThread(void);
void _rebootOnThread(void);
void _flashOnThread(void);
void _cancel(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); }
void _foundBoard(bool firstAttempt, const QSerialPortInfo& portInfo, int type) { emit foundBoard(firstAttempt, portInfo, type); }
void _noBoardFound(void) { emit noBoardFound(); }
void _boardGone(void) { emit boardGone(); }
void _foundBootloader(int bootloaderVersion, int boardID, int flashSize) { emit foundBootloader(bootloaderVersion, boardID, flashSize); }
void _bootloaderSyncFailed(void) { emit bootloaderSyncFailed(); }
void _error(const QString& errorString) { emit error(errorString); }
void _status(const QString& statusText) { emit status(statusText); }
void _eraseStarted(void) { emit eraseStarted(); }
void _eraseComplete(void) { emit eraseComplete(); }
void _flashComplete(void) { emit flashComplete(); }
private:
void _updateProgress(int curr, int total) { emit updateProgress(curr, total); }
PX4FirmwareUpgradeThreadWorker* _worker;
QThread* _workerThread; ///< Thread which PX4FirmwareUpgradeThreadWorker runs on
const FirmwareImage* _image;
};
#endif
......@@ -30,6 +30,7 @@ This file is part of the QGROUNDCONTROL project
#include <QApplication>
#include <QSslSocket>
#include <QSerialPortInfo>
#include "QGCApplication.h"
#include "MainWindow.h"
......@@ -49,6 +50,7 @@ This file is part of the QGROUNDCONTROL project
#undef main
#endif
Q_DECLARE_METATYPE(QSerialPortInfo)
#ifdef Q_OS_WIN
......@@ -107,6 +109,8 @@ int main(int argc, char *argv[])
qRegisterMetaType<QSerialPort::SerialPortError>();
#endif
qRegisterMetaType<QAbstractSocket::SocketError>();
qRegisterMetaType<QSerialPortInfo>();
// We statically link to the google QtLocation plugin
#ifdef Q_OS_WIN
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment