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+= \ ...@@ -591,8 +591,10 @@ HEADERS+= \
!MobileBuild { !MobileBuild {
HEADERS += \ HEADERS += \
src/VehicleSetup/FirmwareUpgradeController.h \ src/VehicleSetup/FirmwareUpgradeController.h \
src/VehicleSetup/PX4Bootloader.h \ src/VehicleSetup/Bootloader.h \
src/VehicleSetup/PX4FirmwareUpgradeThread.h src/VehicleSetup/PX4FirmwareUpgradeThread.h \
src/VehicleSetup/FirmwareImage.h \
} }
SOURCES += \ SOURCES += \
...@@ -621,8 +623,10 @@ SOURCES += \ ...@@ -621,8 +623,10 @@ SOURCES += \
!MobileBuild { !MobileBuild {
SOURCES += \ SOURCES += \
src/VehicleSetup/FirmwareUpgradeController.cc \ src/VehicleSetup/FirmwareUpgradeController.cc \
src/VehicleSetup/PX4Bootloader.cc \ src/VehicleSetup/Bootloader.cc \
src/VehicleSetup/PX4FirmwareUpgradeThread.cc src/VehicleSetup/PX4FirmwareUpgradeThread.cc \
src/VehicleSetup/FirmwareImage.cc \
} }
# Fact System code # Fact System code
......
...@@ -201,6 +201,12 @@ ...@@ -201,6 +201,12 @@
<file alias="QGroundControlConnect">resources/QGroundControlConnect.svg</file> <file alias="QGroundControlConnect">resources/QGroundControlConnect.svg</file>
</qresource> </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"> <qresource prefix="/res/mavs">
<file alias="Helicopter">resources/mavs/helicopter.svg</file> <file alias="Helicopter">resources/mavs/helicopter.svg</file>
<file alias="Unknown">resources/mavs/unknown.svg</file> <file alias="Unknown">resources/mavs/unknown.svg</file>
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
#include "QGCLoggingCategory.h" #include "QGCLoggingCategory.h"
// Add Global logging categories (not class specific) here using QGC_LOGGING_CATEGORY // 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; QGCLoggingCategoryRegister* _instance = NULL;
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#include <QStringList> #include <QStringList>
// Add Global logging categories (not class specific) here using Q_DECLARE_LOGGING_CATEGORY // 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 /// @def QGC_LOGGING_CATEGORY
/// This is a QGC specific replacement for Q_LOGGING_CATEGORY. It will register the category name into a /// This is a QGC specific replacement for Q_LOGGING_CATEGORY. It will register the category name into a
......
...@@ -25,7 +25,8 @@ ...@@ -25,7 +25,8 @@
/// @brief PX4 Bootloader Utility routines /// @brief PX4 Bootloader Utility routines
/// @author Don Gagne <don@thegagnes.com> /// @author Don Gagne <don@thegagnes.com>
#include "PX4Bootloader.h" #include "Bootloader.h"
#include "QGCLoggingCategory.h"
#include <QFile> #include <QFile>
#include <QSerialPortInfo> #include <QSerialPortInfo>
...@@ -78,13 +79,13 @@ static quint32 crc32(const uint8_t *src, unsigned len, unsigned state) ...@@ -78,13 +79,13 @@ static quint32 crc32(const uint8_t *src, unsigned len, unsigned state)
return state; return state;
} }
PX4Bootloader::PX4Bootloader(QObject *parent) : Bootloader::Bootloader(QObject *parent) :
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); qint64 bytesWritten = port->write((const char*)data, maxSize);
if (bytesWritten == -1) { if (bytesWritten == -1) {
...@@ -101,13 +102,13 @@ bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxS ...@@ -101,13 +102,13 @@ bool PX4Bootloader::write(QextSerialPort* port, const uint8_t* data, qint64 maxS
return true; 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 }; 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; qint64 bytesAlreadyRead = 0;
...@@ -137,11 +138,13 @@ bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, in ...@@ -137,11 +138,13 @@ bool PX4Bootloader::read(QextSerialPort* port, uint8_t* data, qint64 maxSize, in
return true; 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]; uint8_t response[2];
if (!read(port, response, 2, responseTimeout)) { if (!_read(port, response, 2, responseTimeout)) {
_errorString.prepend("Get Command Response: "); _errorString.prepend("Get Command Response: ");
return false; return false;
} }
...@@ -164,18 +167,21 @@ bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout ...@@ -164,18 +167,21 @@ bool PX4Bootloader::getCommandResponse(QextSerialPort* port, int responseTimeout
return true; 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 }; uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC };
if (!write(port, buf, sizeof(buf))) { if (!_write(port, buf, sizeof(buf))) {
goto Error; goto Error;
} }
port->flush(); port->flush();
if (!read(port, (uint8_t*)&value, sizeof(value))) { if (!_read(port, (uint8_t*)&value, sizeof(value))) {
goto Error; goto Error;
} }
if (!getCommandResponse(port)) { if (!_getCommandResponse(port)) {
goto Error; goto Error;
} }
...@@ -186,15 +192,18 @@ Error: ...@@ -186,15 +192,18 @@ Error:
return false; 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 }; uint8_t buf[2] = { cmd, PROTO_EOC };
if (!write(port, buf, 2)) { if (!_write(port, buf, 2)) {
goto Error; goto Error;
} }
port->flush(); port->flush();
if (!getCommandResponse(port, responseTimeout)) { if (!_getCommandResponse(port, responseTimeout)) {
goto Error; goto Error;
} }
...@@ -205,10 +214,10 @@ Error: ...@@ -205,10 +214,10 @@ Error:
return false; return false;
} }
bool PX4Bootloader::erase(QextSerialPort* port) bool Bootloader::erase(QextSerialPort* port)
{ {
// Erase is slow, need larger timeout // 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); _errorString = tr("Board erase failed: %1").arg(_errorString);
return false; return false;
} }
...@@ -216,11 +225,20 @@ bool PX4Bootloader::erase(QextSerialPort* port) ...@@ -216,11 +225,20 @@ bool PX4Bootloader::erase(QextSerialPort* port)
return true; 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)) { 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; return false;
} }
uint32_t imageSize = (uint32_t)firmwareFile.size(); uint32_t imageSize = (uint32_t)firmwareFile.size();
...@@ -248,12 +266,12 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam ...@@ -248,12 +266,12 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam
Q_ASSERT(bytesToSend <= 0x8F); Q_ASSERT(bytesToSend <= 0x8F);
bool failed = true; bool failed = true;
if (write(port, PROTO_PROG_MULTI)) { if (_write(port, PROTO_PROG_MULTI)) {
if (write(port, (uint8_t)bytesToSend)) { if (_write(port, (uint8_t)bytesToSend)) {
if (write(port, imageBuf, bytesToSend)) { if (_write(port, imageBuf, bytesToSend)) {
if (write(port, PROTO_EOC)) { if (_write(port, PROTO_EOC)) {
port->flush(); port->flush();
if (getCommandResponse(port)) { if (_getCommandResponse(port)) {
failed = false; failed = false;
} }
} }
...@@ -270,7 +288,7 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam ...@@ -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. // Calculate the CRC now so we can test it after the board is flashed.
_imageCRC = crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC); _imageCRC = crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC);
emit updateProgramProgress(bytesSent, imageSize); emit updateProgress(bytesSent, imageSize);
} }
firmwareFile.close(); firmwareFile.close();
...@@ -284,46 +302,131 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam ...@@ -284,46 +302,131 @@ bool PX4Bootloader::program(QextSerialPort* port, const QString& firmwareFilenam
return true; 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; bool ret;
if (_bootloaderVersion <= 2) { if (!image->imageIsBinFormat() || _bootloaderVersion <= 2) {
ret = _bootloaderVerifyRev2(port, firmwareFilename); ret = _verifyBytes(port, image);
} else { } else {
ret = _bootloaderVerifyRev3(port); ret = _verifyCRC(port);
} }
sendBootloaderReboot(port); reboot(port);
return ret; return ret;
} }
/// @brief Verify the flash on bootloader version 2 by reading it back and comparing it against /// @brief Verify the flash on bootloader eading it back and comparing it against the original image->
/// the original firmware file. bool Bootloader::_verifyBytes(QextSerialPort* port, const FirmwareImage* image)
bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString firmwareFilename) {
if (image->imageIsBinFormat()) {
return _binVerifyBytes(port, image);
} else {
return _ihxVerifyBytes(port, image);
}
}
bool Bootloader::_binVerifyBytes(QextSerialPort* port, const FirmwareImage* image)
{ {
QFile firmwareFile(firmwareFilename); Q_ASSERT(image->imageIsBinFormat());
QFile firmwareFile(image->binFilename());
if (!firmwareFile.open(QIODevice::ReadOnly)) { 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; return false;
} }
uint32_t imageSize = (uint32_t)firmwareFile.size(); uint32_t imageSize = (uint32_t)firmwareFile.size();
if (!sendCommand(port, PROTO_CHIP_VERIFY)) { if (!_sendCommand(port, PROTO_CHIP_VERIFY)) {
return false; return false;
} }
uint8_t fileBuf[READ_MULTI_MAX]; uint8_t fileBuf[READ_MULTI_MAX];
uint8_t flashBuf[READ_MULTI_MAX]; uint8_t readBuf[READ_MULTI_MAX];
uint32_t bytesVerified = 0; uint32_t bytesVerified = 0;
Q_ASSERT(PROG_MULTI_MAX <= 0x8F); Q_ASSERT(PROG_MULTI_MAX <= 0x8F);
while (bytesVerified < imageSize) { while (bytesVerified < imageSize) {
int bytesToRead = imageSize - bytesVerified; int bytesToRead = imageSize - bytesVerified;
if (bytesToRead > (int)sizeof(fileBuf)) { if (bytesToRead > (int)sizeof(readBuf)) {
bytesToRead = (int)sizeof(fileBuf); bytesToRead = (int)sizeof(readBuf);
} }
Q_ASSERT((bytesToRead % 4) == 0); Q_ASSERT((bytesToRead % 4) == 0);
...@@ -337,48 +440,137 @@ bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString fi ...@@ -337,48 +440,137 @@ bool PX4Bootloader::_bootloaderVerifyRev2(QextSerialPort* port, const QString fi
Q_ASSERT(bytesToRead <= 0x8F); Q_ASSERT(bytesToRead <= 0x8F);
bool failed = true; bool failed = true;
if (write(port, PROTO_READ_MULTI)) { if (_write(port, PROTO_READ_MULTI) &&
if (write(port, (uint8_t)bytesToRead)) { _write(port, (uint8_t)bytesToRead) &&
if (write(port, PROTO_EOC)) { _write(port, PROTO_EOC)) {
port->flush(); port->flush();
if (read(port, flashBuf, sizeof(flashBuf))) { if (_read(port, readBuf, sizeof(readBuf))) {
if (getCommandResponse(port)) { if (_getCommandResponse(port)) {
failed = false; failed = false;
} }
} }
} }
if (failed) {
_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] != 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;
}
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) { if (failed) {
_errorString = tr("Verify failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0')); _errorString = QString("Unable to set read start address: 0x%2").arg(readAddress, 8, 16, QLatin1Char('0'));
return false; 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++) { for (int i=0; i<bytesToRead; i++) {
if (fileBuf[i] != flashBuf[i]) { if ((uint8_t)imageBytes[bytesIndex + i] != readBuf[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')); _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; return false;
} }
} }
bytesVerified += bytesToRead; bytesVerified += bytesToRead;
bytesIndex += bytesToRead;
bytesLeftToRead -= bytesToRead;
emit updateProgress(bytesVerified, imageSize);
}
} }
firmwareFile.close();
return true; return true;
} }
/// @Brief Verify the flash on a version 3 or higher bootloader board by comparing CRCs. /// @Brief Verify the flash by comparing CRCs.
bool PX4Bootloader::_bootloaderVerifyRev3(QextSerialPort* port) bool Bootloader::_verifyCRC(QextSerialPort* port)
{ {
uint8_t buf[2] = { PROTO_GET_CRC, PROTO_EOC }; uint8_t buf[2] = { PROTO_GET_CRC, PROTO_EOC };
quint32 flashCRC; quint32 flashCRC;
bool failed = true; bool failed = true;
if (write(port, buf, 2)) { if (_write(port, buf, 2)) {
port->flush(); port->flush();
if (read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) { if (_read(port, (uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) {
if (getCommandResponse(port)) { if (_getCommandResponse(port)) {
failed = false; failed = false;
} }
} }
...@@ -395,7 +587,7 @@ bool PX4Bootloader::_bootloaderVerifyRev3(QextSerialPort* port) ...@@ -395,7 +587,7 @@ bool PX4Bootloader::_bootloaderVerifyRev3(QextSerialPort* port)
return true; return true;
} }
bool PX4Bootloader::open(QextSerialPort* port, const QString portName) bool Bootloader::open(QextSerialPort* port, const QString portName)
{ {
Q_ASSERT(!port->isOpen()); Q_ASSERT(!port->isOpen());
...@@ -414,10 +606,10 @@ bool PX4Bootloader::open(QextSerialPort* port, const QString portName) ...@@ -414,10 +606,10 @@ bool PX4Bootloader::open(QextSerialPort* port, const QString portName)
return true; return true;
} }
bool PX4Bootloader::sync(QextSerialPort* port) bool Bootloader::sync(QextSerialPort* port)
{ {
// Send sync command // Send sync command
if (sendCommand(port, PROTO_GET_SYNC)) { if (_sendCommand(port, PROTO_GET_SYNC)) {
return true; return true;
} else { } else {
_errorString.prepend("Sync: "); _errorString.prepend("Sync: ");
...@@ -425,10 +617,10 @@ bool PX4Bootloader::sync(QextSerialPort* port) ...@@ -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; goto Error;
} }
if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) { if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) {
...@@ -436,11 +628,11 @@ bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersi ...@@ -436,11 +628,11 @@ bool PX4Bootloader::getBoardInfo(QextSerialPort* port, uint32_t& bootloaderVersi
goto Error; goto Error;
} }
if (!getBoardInfo(port, INFO_BOARD_ID, _boardID)) { if (!_getPX4BoardInfo(port, INFO_BOARD_ID, _boardID)) {
goto Error; goto Error;
} }
if (!getBoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) { if (!_getPX4BoardInfo(port, INFO_FLASH_SIZE, _boardFlashSize)) {
qWarning() << _errorString; qWarning() << _errorString;
goto Error; goto Error;
} }
...@@ -456,7 +648,35 @@ Error: ...@@ -456,7 +648,35 @@ Error:
return false; 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,88 +22,87 @@ ...@@ -22,88 +22,87 @@
======================================================================*/ ======================================================================*/
/// @file /// @file
/// @brief PX4 Bootloader Utility routines
/// @author Don Gagne <don@thegagnes.com> /// @author Don Gagne <don@thegagnes.com>
#ifndef PX4Bootloader_H #ifndef Bootloader_H
#define PX4Bootloader_H #define Bootloader_H
#include "FirmwareImage.h"
#include "qextserialport.h" #include "qextserialport.h"
#include <stdint.h> #include <stdint.h>
/// @brief This class is used to communicate with the Bootloader. /// Bootloader Utility routines. Works with PX4 bootloader and 3DR Radio bootloader.
class PX4Bootloader : public QObject class Bootloader : public QObject
{ {
Q_OBJECT Q_OBJECT
public: 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 /// @brief Returns the error message associated with the last failed call to one of the bootloader
/// utility routine below. /// utility routine below.
QString errorString(void) { return _errorString; } QString errorString(void) { return _errorString; }
/// @brief Write a byte to the port /// @brief Opens a port to the bootloader
/// @param port Port to write to bool open(QextSerialPort* port, const QString portName);
/// @param data Bytes to write
/// @param maxSize Number of bytes to write
/// @return true: success
bool write(QextSerialPort* port, const uint8_t* data, qint64 maxSize);
bool write(QextSerialPort* port, const uint8_t byte);
/// @brief Read a set of bytes from the port
/// @param data Read bytes into this buffer
/// @param maxSize Number of bytes to read
/// @param readTimeout Msecs to wait for bytes to become available on port
bool read(QextSerialPort* port, uint8_t* data, qint64 maxSize, int readTimeout = _readTimout);
/// @brief Read a PROTO_SYNC command response from the bootloader
/// @param responseTimeout Msecs to wait for response bytes to become available on port
bool getCommandResponse(QextSerialPort* port, const int responseTimeout = _responseTimeout);
/// @brief Send a PROTO_GET_DEVICE command to retrieve a value from the bootloader
/// @param param Value to retrieve using INFO_BOARD_* enums
/// @param value Returned value
bool getBoardInfo(QextSerialPort* port, uint8_t param, uint32_t& value);
/// @brief Send a command to the bootloader
/// @param cmd Command to send using PROTO_* enums
/// @return true: Command sent and valid sync response returned
bool sendCommand(QextSerialPort* port, uint8_t cmd, int responseTimeout = _responseTimeout);
/// @brief Program the board with the specified firmware
bool program(QextSerialPort* port, const QString& firmwareFilename);
/// @brief Verify the board flash. How it works depend on bootloader rev
/// Rev 2: Read the flash back and compare it against the firmware file
/// Rev 3: Compare CRCs for flash and file
bool verify(QextSerialPort* port, const QString firmwareFilename);
/// @brief Read a PROTO_SYNC response from the bootloader /// @brief Read a PROTO_SYNC response from the bootloader
/// @return true: Valid sync response was received /// @return true: Valid sync response was received
bool sync(QextSerialPort* port); 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 bootloaderVersion Returned INFO_BL_REV
/// @param boardID Returned INFO_BOARD_ID /// @param boardID Returned INFO_BOARD_ID
/// @param flashSize Returned INFO_FLASH_SIZE /// @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 /// @brief Retrieve the board id from a 3DR Radio
bool open(QextSerialPort* port, const QString portName); bool get3DRRadioBoardId(QextSerialPort* port, uint32_t& boardID);
/// @brief Sends a PROTO_REBOOT command to the bootloader /// @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 // Supported bootloader board ids
bool erase(QextSerialPort* port); 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: signals:
/// @brief Signals progress indicator for long running bootloader utility routines /// @brief Signals progress indicator for long running bootloader utility routines
void updateProgramProgress(int curr, int total); void updateProgress(int curr, int total);
private: 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 { enum {
// protocol bytes // protocol bytes
PROTO_INSYNC = 0x12, ///< 'in sync' byte sent before status PROTO_INSYNC = 0x12, ///< 'in sync' byte sent before status
...@@ -118,6 +117,7 @@ private: ...@@ -118,6 +117,7 @@ private:
PROTO_GET_SYNC = 0x21, ///< NOP for re-establishing sync PROTO_GET_SYNC = 0x21, ///< NOP for re-establishing sync
PROTO_GET_DEVICE = 0x22, ///< get device ID bytes PROTO_GET_DEVICE = 0x22, ///< get device ID bytes
PROTO_CHIP_ERASE = 0x23, ///< erase program area and reset program address 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_PROG_MULTI = 0x27, ///< write bytes at program address and increment
PROTO_GET_CRC = 0x29, ///< compute & return a CRC PROTO_GET_CRC = 0x29, ///< compute & return a CRC
PROTO_BOOT = 0x30, ///< boot the application PROTO_BOOT = 0x30, ///< boot the application
...@@ -133,15 +133,10 @@ private: ...@@ -133,15 +133,10 @@ private:
INFO_BOARD_REV = 3, ///< board revision INFO_BOARD_REV = 3, ///< board revision
INFO_FLASH_SIZE = 4, ///< max firmware size in bytes INFO_FLASH_SIZE = 4, ///< max firmware size in bytes
PROG_MULTI_MAX = 32, ///< write size for PROTO_PROG_MULTI, must be multiple of 4 PROG_MULTI_MAX = 64, ///< 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 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 _boardID; ///< board id for currently connected board
uint32_t _boardFlashSize; ///< flash size for currently connected board uint32_t _boardFlashSize; ///< flash size for currently connected board
uint32_t _imageCRC; ///< CRC for image in currently selected firmware file uint32_t _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 ...@@ -34,158 +34,337 @@ import QGroundControl.Controllers 1.0
import QGroundControl.ScreenTools 1.0 import QGroundControl.ScreenTools 1.0
QGCView { QGCView {
id: qgcView
viewPanel: panel 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 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 } QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled }
FirmwareUpgradeController { FirmwareUpgradeController {
id: controller id: controller
upgradeButton: upgradeButton
progressBar: progressBar progressBar: progressBar
statusLog: statusTextArea statusLog: statusTextArea
firmwareType: FirmwareUpgradeController.StableFirmware
onShowMessage: { Component.onCompleted: {
showMessage(title, message, StandardButton.Ok) controllerCompleted = true
if (qgcView.completedSignalled) {
// We can only start the board search when the Qml and Controller are completely done loading
controller.startBoardSearch()
} }
} }
QGCViewPanel { onNoBoardFound: {
id: panel initialBoardSearch = false
anchors.fill: parent statusTextArea.append(plugInText)
}
Component { onBoardGone: {
id: firmwareWarningComponent initialBoardSearch = false
statusTextArea.append(plugInText)
}
QGCViewMessage { onBoardFound: {
message: firmwareWarningMessage 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)
}
}
}
function accept() { onError: {
hideDialog() hideDialog()
controller.doFirmwareUpgrade(); flashCompleteWaitTimer.running = true
}
onFlashComplete: flashCompleteWaitTimer.running = true
}
onCompleted: {
if (controllerCompleted) {
// We can only start the board search when the Qml and Controller are completely done loading
controller.startBoardSearch()
} }
} }
// 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.
Timer {
id: flashCompleteWaitTimer
interval: 15000
onTriggered: {
initialBoardSearch = true
progressBar.value = 0
statusTextArea.append(welcomeText)
controller.startBoardSearch()
}
} }
Column { Component {
id: pixhawkFirmwareSelectDialog
QGCViewDialog {
anchors.fill: parent anchors.fill: parent
QGCLabel { property bool showVersionSelection: apmFlightStack.checked || advancedMode.checked
text: "FIRMWARE UPDATE"
font.pixelSize: ScreenTools.largeFontPixelSize function accept() {
hideDialog()
controller.flash(firmwareVersionCombo.model.get(firmwareVersionCombo.currentIndex).firmwareType)
} }
Item { function reject() {
// Just used as a spacer cancelFlash()
height: 20 hideDialog()
width: 10
} }
Row { ExclusiveGroup {
spacing: 10 id: firmwareGroup
}
ListModel { ListModel {
id: firmwareItems id: px4FirmwareTypeList
ListElement { ListElement {
text: qsTr("Standard Version (stable)"); text: qsTr("Standard Version (stable)");
firmwareType: FirmwareUpgradeController.StableFirmware firmwareType: FirmwareUpgradeController.PX4StableFirmware
} }
ListElement { ListElement {
text: qsTr("Beta Testing (beta)"); text: qsTr("Beta Testing (beta)");
firmwareType: FirmwareUpgradeController.BetaFirmware firmwareType: FirmwareUpgradeController.PX4BetaFirmware
} }
ListElement { ListElement {
text: qsTr("Developer Build (master)"); text: qsTr("Developer Build (master)");
firmwareType: FirmwareUpgradeController.DeveloperFirmware firmwareType: FirmwareUpgradeController.PX4DeveloperFirmware
} }
ListElement { ListElement {
text: qsTr("Custom firmware file..."); text: qsTr("Custom firmware file...");
firmwareType: FirmwareUpgradeController.CustomFirmware firmwareType: FirmwareUpgradeController.PX4CustomFirmware
} }
} }
QGCComboBox { ListModel {
id: firmwareCombo id: apmFirmwareTypeList
width: 200
height: upgradeButton.height ListElement {
model: firmwareItems 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
}
} }
QGCButton { Column {
id: upgradeButton anchors.fill: parent
text: "UPGRADE" spacing: defaultTextHeight
primary: true
onClicked: { QGCLabel {
if (controller.activeQGCConnections()) { width: parent.width
showMessage("Firmware Upgrade", wrapMode: Text.WordWrap
"There are still vehicles connected to QGroundControl. " + text: "Detected Pixhawk board. You can select from the following flight stacks:"
"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
}
QGCRadioButton {
id: px4FlightStack
checked: true
exclusiveGroup: firmwareGroup
text: "PX4 Flight Stack (full QGC support)"
onClicked: parent.firmwareVersionChanged(px4FirmwareTypeList)
} }
if (controller.pluggedInBoard()) { QGCRadioButton {
showMessage("Firmware Upgrade", id: apmFlightStack
"You vehicle is currently connected via USB. " + exclusiveGroup: firmwareGroup
"You must unplug your vehicle from USB prior to Firmware Upgrade.", text: "APM Flight Stack (partial QGC support)"
StandardButton.Ok)
return onClicked: parent.firmwareVersionChanged(apmFirmwareTypeList)
} }
controller.firmwareType = firmwareItems.get(firmwareCombo.currentIndex).firmwareType 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
if (controller.firmwareType == 1) { onActivated: {
firmwareWarningMessage = "WARNING: BETA FIRMWARE\n" + if (model.get(index).firmwareType == FirmwareUpgradeController.PX4BetaFirmware) {
firmwareVersionWarningLabel.visible = true
firmwareVersionWarningLabel.text = "WARNING: BETA FIRMWARE. " +
"This firmware version is ONLY intended for beta testers. " + "This firmware version is ONLY intended for beta testers. " +
"Although it has received FLIGHT TESTING, it represents actively changed code. " + "Although it has received FLIGHT TESTING, it represents actively changed code. " +
"Do NOT use for normal operation.\n\n" + "Do NOT use for normal operation."
"Click Cancel to abort upgrade, Click Ok to Upgrade anwyay" } else if (model.get(index).firmwareType == FirmwareUpgradeController.PX4DeveloperFirmware) {
showDialog(firmwareWarningComponent, "Firmware Upgrade", 50, StandardButton.Cancel | StandardButton.Ok) firmwareVersionWarningLabel.visible = true
} else if (controller.firmwareType == 2) { firmwareVersionWarningLabel.text = "WARNING: CONTINUOUS BUILD FIRMWARE. " +
firmwareWarningMessage = "WARNING: CONTINUOUS BUILD FIRMWARE\n" +
"This firmware has NOT BEEN FLIGHT TESTED. " + "This firmware has NOT BEEN FLIGHT TESTED. " +
"It is only intended for DEVELOPERS. " + "It is only intended for DEVELOPERS. " +
"Run bench tests without props first. " + "Run bench tests without props first. " +
"Do NOT fly this without addional safety precautions. " + "Do NOT fly this without addional safety precautions. " +
"Follow the mailing list actively when using it.\n\n" + "Follow the mailing list actively when using it."
"Click Cancel to abort upgrade, Click Ok to Upgrade anwyay"
showDialog(firmwareWarningComponent, "Firmware Upgrade", 50, StandardButton.Cancel | StandardButton.Ok)
} else { } else {
controller.doFirmwareUpgrade(); 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
}
} }
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
function accept() {
hideDialog()
controller.doFirmwareUpgrade();
} }
} }
} }
Item { QGCViewPanel {
// Just used as a spacer id: panel
height: 20 anchors.fill: parent
width: 10
QGCLabel {
id: titleLabel
text: title
font.pixelSize: ScreenTools.largeFontPixelSize
} }
ProgressBar { ProgressBar {
id: progressBar id: progressBar
anchors.topMargin: ScreenTools.defaultFontPixelHeight
anchors.top: titleLabel.bottom
width: parent.width width: parent.width
} }
TextArea { TextArea {
id: statusTextArea id: statusTextArea
anchors.topMargin: ScreenTools.defaultFontPixelHeight
anchors.top: progressBar.bottom
anchors.bottom: parent.bottom
width: parent.width width: parent.width
height: 300
readOnly: true readOnly: true
frameVisible: false frameVisible: false
font.pixelSize: ScreenTools.defaultFontPixelSize font.pixelSize: ScreenTools.defaultFontPixelSize
textFormat: TextEdit.RichText
text: qsTr("Please disconnect all vehicles from QGroundControl before selecting Upgrade.") text: welcomeText
style: TextAreaStyle { style: TextAreaStyle {
textColor: qgcPal.text textColor: qgcPal.text
backgroundColor: qgcPal.windowShade backgroundColor: qgcPal.windowShade
} }
} }
} // Column } // QGCViewPabel
} // QGCViewPanel
} // QGCView } // QGCView
\ No newline at end of file
...@@ -26,16 +26,7 @@ ...@@ -26,16 +26,7 @@
/// @author Don Gagne <don@thegagnes.com> /// @author Don Gagne <don@thegagnes.com>
#include "FirmwareUpgradeController.h" #include "FirmwareUpgradeController.h"
#include "Bootloader.h"
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include <QQmlProperty>
#include <QDebug>
#include "QGCFileDialog.h" #include "QGCFileDialog.h"
#include "QGCMessageBox.h" #include "QGCMessageBox.h"
...@@ -43,163 +34,191 @@ ...@@ -43,163 +34,191 @@
FirmwareUpgradeController::FirmwareUpgradeController(void) : FirmwareUpgradeController::FirmwareUpgradeController(void) :
_downloadManager(NULL), _downloadManager(NULL),
_downloadNetworkReply(NULL), _downloadNetworkReply(NULL),
_firmwareType(StableFirmware), _statusLog(NULL),
_upgradeButton(NULL), _image(NULL)
_statusLog(NULL)
{ {
_threadController = new PX4FirmwareUpgradeThreadController(this); _threadController = new PX4FirmwareUpgradeThreadController(this);
Q_CHECK_PTR(_threadController); Q_CHECK_PTR(_threadController);
connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &FirmwareUpgradeController::_foundBoard); 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::foundBootloader, this, &FirmwareUpgradeController::_foundBootloader);
connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed, this, &FirmwareUpgradeController::_bootloaderSyncFailed); connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed, this, &FirmwareUpgradeController::_bootloaderSyncFailed);
connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error); connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error);
connect(_threadController, &PX4FirmwareUpgradeThreadController::complete, this, &FirmwareUpgradeController::_complete); connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
connect(_threadController, &PX4FirmwareUpgradeThreadController::findTimeout, this, &FirmwareUpgradeController::_findTimeout); 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(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
connect(LinkManager::instance(), &LinkManager::linkDisconnected, this, &FirmwareUpgradeController::_linkDisconnected);
connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick); connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
} }
/// @brief Cancels the current state and returns to the begin start void FirmwareUpgradeController::startBoardSearch(void)
void FirmwareUpgradeController::_cancel(void)
{ {
// Bootloader may still still open, reboot to close and heopfully get back to FMU _bootloaderFound = false;
_threadController->sendBootloaderReboot(); _startFlashWhenBootloaderFound = false;
_threadController->startFindBoardLoop();
Q_ASSERT(_upgradeButton);
_upgradeButton->setEnabled(true);
} }
/// @brief Begins the process or searching for the board void FirmwareUpgradeController::flash(FirmwareType_t firmwareType)
void FirmwareUpgradeController::_findBoard(void)
{ {
QString msg("Plug your board into USB now. Press Ok when board is plugged in."); if (_bootloaderFound) {
_getFirmwareFile(firmwareType);
_appendStatusLog(msg); } else {
emit showMessage("Firmware Upgrade", msg); // We haven't found the bootloader yet. Need to wait until then to flash
_startFlashWhenBootloaderFound = true;
_startFlashWhenBootloaderFoundFirmwareType = firmwareType;
}
}
_searchingForBoard = true; void FirmwareUpgradeController::cancel(void)
_threadController->findBoard(_findBoardTimeoutMsec); {
_eraseTimer.stop();
_threadController->cancel();
} }
/// @brief Called when board has been found by the findBoard process void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int type)
void FirmwareUpgradeController::_foundBoard(bool firstTry, const QString portName, QString portDescription)
{ {
if (firstTry) { _foundBoardInfo = info;
// Board is still plugged switch (type) {
_cancel(); case FoundBoardPX4FMUV1:
emit showMessage("Board plugged in", _foundBoardType = "PX4 FMU V1";
"Please unplug your board before beginning the Firmware Upgrade process. " _startFlashWhenBootloaderFound = false;
"Click Upgrade again once the board is unplugged."); break;
} else { case FoundBoardPX4FMUV2:
_portName = portName; _foundBoardType = "Pixhawk";
_portDescription = portDescription; _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;
}
_appendStatusLog(tr("Board found:")); qCDebug(FirmwareUpgradeLog) << _foundBoardType;
_appendStatusLog(tr(" Port: %1").arg(_portName)); emit boardFound();
_appendStatusLog(tr(" Description: %1").arg(_portName)); }
_findBootloader();
} void FirmwareUpgradeController::_noBoardFound(void)
{
emit noBoardFound();
} }
/// @brief Begins the findBootloader process to connect to the bootloader void FirmwareUpgradeController::_boardGone(void)
void FirmwareUpgradeController::_findBootloader(void)
{ {
_appendStatusLog(tr("Attemping to communicate with bootloader...")); emit boardGone();
_searchingForBoard = false;
_threadController->findBootloader(_portName, _findBootloaderTimeoutMsec);
} }
/// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine /// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine
/// to the next step. /// to the next step.
void FirmwareUpgradeController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize) void FirmwareUpgradeController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize)
{ {
_bootloaderFound = true;
_bootloaderVersion = bootloaderVersion; _bootloaderVersion = bootloaderVersion;
_boardID = boardID; _bootloaderBoardID = boardID;
_boardFlashSize = flashSize; _bootloaderBoardFlashSize = flashSize;
_appendStatusLog(tr("Connected to bootloader:")); _appendStatusLog("Connected to bootloader:");
_appendStatusLog(tr(" Version: %1").arg(_bootloaderVersion)); _appendStatusLog(QString(" Version: %1").arg(_bootloaderVersion));
_appendStatusLog(tr(" Board ID: %1").arg(_boardID)); _appendStatusLog(QString(" Board ID: %1").arg(_bootloaderBoardID));
_appendStatusLog(tr(" Flash size: %1").arg(_boardFlashSize)); _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 /// @brief Called when the findBootloader process is unable to sync to the bootloader. Moves the state
/// machine to the appropriate error state. /// machine to the appropriate error state.
void FirmwareUpgradeController::_bootloaderSyncFailed(void) void FirmwareUpgradeController::_bootloaderSyncFailed(void)
{ {
_appendStatusLog(tr("Unable to sync with bootloader.")); _errorCancel("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);
} }
/// @brief Prompts the user to select a firmware file if needed and moves the state machine to the next state. /// @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] = static DownloadLocationByFirmwareType_t rgPX4FMUV2Firmware[] = {
{ { PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v2_default.px4" },
"http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v1_default.px4", { PX4BetaFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v2_default.px4" },
"http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v1_default.px4", { PX4DeveloperFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v2_default.px4"},
"http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v1_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 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 size_t crgAeroCoreFirmware = sizeof(rgAeroCoreFirmware) / sizeof(rgAeroCoreFirmware[0]);
static const char* rgPX4FMUV2Firmware[3] = static const DownloadLocationByFirmwareType_t rgPX4FMUV1Firmware[] = {
{ { PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v1_default.px4" },
"http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v2_default.px4", { PX4BetaFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v1_default.px4" },
"http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v2_default.px4", { PX4DeveloperFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v1_default.px4" },
"http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v2_default.px4"
}; };
static const size_t crgPX4FMUV1Firmware = sizeof(rgPX4FMUV1Firmware) / sizeof(rgPX4FMUV1Firmware[0]);
static const char* rgAeroCoreFirmware[3] = static const DownloadLocationByFirmwareType_t rgPX4FlowFirmware[] = {
{ { PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4" },
"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 crgPX4FlowFirmware = sizeof(rgPX4FlowFirmware) / sizeof(rgPX4FlowFirmware[0]);
static const char* rgPX4FlowFirmware[3] = static const DownloadLocationByFirmwareType_t rg3DRRadioFirmware[] = {
{ { PX4StableFirmware, "http://firmware.diydrones.com/SiK/stable/radio~hm_trp.ihx" },
"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 crg3DRRadioFirmware = sizeof(rg3DRRadioFirmware) / sizeof(rg3DRRadioFirmware[0]);
Q_ASSERT(sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FMUV2Firmware) && sizeof(rgPX4FMUV1Firmware) == sizeof(rgPX4FlowFirmware)); // Select the firmware set based on board type
const char** prgFirmware; const DownloadLocationByFirmwareType_t* prgFirmware;
switch (_boardID) { size_t crgFirmware;
case _boardIDPX4FMUV1:
switch (_bootloaderBoardID) {
case Bootloader::boardIDPX4FMUV1:
prgFirmware = rgPX4FMUV1Firmware; prgFirmware = rgPX4FMUV1Firmware;
crgFirmware = crgPX4FMUV1Firmware;
break; break;
case _boardIDPX4Flow: case Bootloader::boardIDPX4Flow:
prgFirmware = rgPX4FlowFirmware; prgFirmware = rgPX4FlowFirmware;
crgFirmware = crgPX4FlowFirmware;
break; break;
case _boardIDPX4FMUV2: case Bootloader::boardIDPX4FMUV2:
prgFirmware = rgPX4FMUV2Firmware; prgFirmware = rgPX4FMUV2Firmware;
crgFirmware = crgPX4FMUV2Firmware;
break; break;
case _boardIDAeroCore: case Bootloader::boardIDAeroCore:
prgFirmware = rgAeroCoreFirmware; prgFirmware = rgAeroCoreFirmware;
crgFirmware = crgAeroCoreFirmware;
break;
case Bootloader::boardID3DRRadio:
prgFirmware = rg3DRRadioFirmware;
crgFirmware = crg3DRRadioFirmware;
break; break;
default: default:
...@@ -207,23 +226,37 @@ void FirmwareUpgradeController::_getFirmwareFile(void) ...@@ -207,23 +226,37 @@ void FirmwareUpgradeController::_getFirmwareFile(void)
break; break;
} }
if (prgFirmware == NULL && _firmwareType != CustomFirmware) { if (prgFirmware == NULL && firmwareType != PX4CustomFirmware) {
QGCMessageBox::critical(tr("Firmware Upgrade"), tr("Attemping to flash an unknown board type, you must select 'Custom firmware file'")); _errorCancel("Attempting to flash an unknown board type, you must select 'Custom firmware file'");
_cancel();
return; return;
} }
if (_firmwareType == CustomFirmware) { if (firmwareType == PX4CustomFirmware) {
_firmwareFilename = QGCFileDialog::getOpenFileName(NULL, // Parent to main window _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 QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), // Initial directory
tr("Firmware Files (*.px4 *.bin)")); // File filter "Firmware Files (*.px4 *.bin *.ihx)"); // File filter
} else {
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 { } else {
_firmwareFilename = prgFirmware[_firmwareType]; _errorCancel("Unable to find specified firmware download location");
return;
}
} }
if (_firmwareFilename.isEmpty()) { if (_firmwareFilename.isEmpty()) {
_cancel(); _errorCancel("No firmware file selected");
} else { } else {
_downloadFirmware(); _downloadFirmware();
} }
...@@ -234,8 +267,8 @@ void FirmwareUpgradeController::_downloadFirmware(void) ...@@ -234,8 +267,8 @@ void FirmwareUpgradeController::_downloadFirmware(void)
{ {
Q_ASSERT(!_firmwareFilename.isEmpty()); Q_ASSERT(!_firmwareFilename.isEmpty());
_appendStatusLog(tr("Downloading firmware...")); _appendStatusLog("Downloading firmware...");
_appendStatusLog(tr(" From: %1").arg(_firmwareFilename)); _appendStatusLog(QString(" From: %1").arg(_firmwareFilename));
// Split out filename from path // Split out filename from path
QString firmwareFilename = QFileInfo(_firmwareFilename).fileName(); QString firmwareFilename = QFileInfo(_firmwareFilename).fileName();
...@@ -246,8 +279,7 @@ void FirmwareUpgradeController::_downloadFirmware(void) ...@@ -246,8 +279,7 @@ void FirmwareUpgradeController::_downloadFirmware(void)
if (downloadFile.isEmpty()) { if (downloadFile.isEmpty()) {
downloadFile = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); downloadFile = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
if (downloadFile.isEmpty()) { if (downloadFile.isEmpty()) {
_appendStatusLog(tr("Unabled to find writable download location. Tried downloads and temp directory.")); _errorCancel("Unabled to find writable download location. Tried downloads and temp directory.");
_cancel();
return; return;
} }
} }
...@@ -290,7 +322,7 @@ void FirmwareUpgradeController::_downloadProgress(qint64 curr, qint64 total) ...@@ -290,7 +322,7 @@ void FirmwareUpgradeController::_downloadProgress(qint64 curr, qint64 total)
/// @brief Called when the firmware download completes. /// @brief Called when the firmware download completes.
void FirmwareUpgradeController::_downloadFinished(void) void FirmwareUpgradeController::_downloadFinished(void)
{ {
_appendStatusLog(tr("Download complete")); _appendStatusLog("Download complete");
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
Q_ASSERT(reply); Q_ASSERT(reply);
...@@ -313,286 +345,72 @@ void FirmwareUpgradeController::_downloadFinished(void) ...@@ -313,286 +345,72 @@ void FirmwareUpgradeController::_downloadFinished(void)
// Store downloaded file in download location // Store downloaded file in download location
QFile file(downloadFilename); QFile file(downloadFilename);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
_appendStatusLog(tr("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString())); _errorCancel(QString("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString()));
_cancel();
return; return;
} }
file.write(reply->readAll()); file.write(reply->readAll());
file.close(); 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")) { if (!image->load(downloadFilename, _bootloaderBoardID)) {
// We need to collect information from the .px4 file as well as pull the binary image out to a seperate file. _errorCancel("Image load failed");
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; return;
} }
// Decompress the parameter xml and save to file // We can't proceed unless we have the bootloader
QByteArray decompressedBytes; if (!_bootloaderFound) {
bool success = _decompressJsonValue(px4Json, // JSON object _errorCancel(QString("Bootloader not found").arg(_imageSize).arg(_bootloaderBoardFlashSize));
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; return;
} }
qint64 bytesWritten = decompressFile.write(decompressedBytes); if (_bootloaderBoardFlashSize != 0 && _imageSize > _bootloaderBoardFlashSize) {
if (bytesWritten != decompressedBytes.count()) { _errorCancel(QString("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_bootloaderBoardFlashSize));
_appendStatusLog(tr("Write failed for decompressed image file, error: %1").arg(decompressFile.errorString()));
_cancel();
return; 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) { _threadController->flash(image);
_appendStatusLog(tr("Downloaded firmware board id does not match hardware board id: %1 != %2").arg(firmwareBoardID).arg(_boardID));
_cancel();
return;
}
_firmwareFilename = downloadFilename;
QFile binFile(_firmwareFilename);
if (!binFile.open(QIODevice::ReadOnly)) {
_appendStatusLog(tr("Unabled to open firmware file %1, %2").arg(_firmwareFilename).arg(binFile.errorString()));
_cancel();
return;
}
_imageSize = (uint32_t)binFile.size();
binFile.close();
} else {
// Standard firmware builds (stable/continuous/...) are always .bin or .px4. Select file dialog for custom
// firmware filters to .bin and .px4. So we should never get a file that ends in anything else.
Q_ASSERT(false);
}
if (_imageSize > _boardFlashSize) {
_appendStatusLog(tr("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_boardFlashSize));
_cancel();
return;
}
_erase();
}
/// 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;
}
_appendStatusLog(QString("Succesfully decompressed %1").arg(bytesKey));
return true;
} }
/// @brief Called when an error occurs during download /// @brief Called when an error occurs during download
void FirmwareUpgradeController::_downloadError(QNetworkReply::NetworkError code) void FirmwareUpgradeController::_downloadError(QNetworkReply::NetworkError code)
{ {
QString errorMsg;
if (code == QNetworkReply::OperationCanceledError) { if (code == QNetworkReply::OperationCanceledError) {
_appendStatusLog(tr("Download cancelled")); errorMsg = "Download cancelled";
} else { } 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 /// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the
void FirmwareUpgradeController::_erase(void) /// appropriate next step.
void FirmwareUpgradeController::_flashComplete(void)
{ {
_appendStatusLog(tr("Erasing previous firmware...")); delete _image;
_image = NULL;
// We set up our own progress bar for erase since the erase command does not provide one
_eraseTickCount = 0;
_eraseTimer.start(_eraseTickMsec);
// Erase command _appendStatusLog("Upgrade complete", true);
_threadController->erase(); _appendStatusLog("------------------------------------------", false);
emit flashComplete();
} }
/// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the void FirmwareUpgradeController::_error(const QString& errorString)
/// appropriate next step.
void FirmwareUpgradeController::_complete(const int command)
{ {
if (command == PX4FirmwareUpgradeThreadWorker::commandProgram) { delete _image;
_appendStatusLog(tr("Verifying board programming...")); _image = NULL;
_threadController->verify(_firmwareFilename);
} else if (command == PX4FirmwareUpgradeThreadWorker::commandVerify) { _errorCancel(QString("Error: %1").arg(errorString));
_appendStatusLog(tr("Upgrade complete"));
QGCMessageBox::information(tr("Firmware Upgrade"), tr("Upgrade completed succesfully"));
_cancel();
} else if (command == PX4FirmwareUpgradeThreadWorker::commandErase) {
_eraseTimer.stop();
_appendStatusLog(tr("Flashing new firmware to board..."));
_threadController->program(_firmwareFilename);
} else if (command == PX4FirmwareUpgradeThreadWorker::commandCancel) {
// FIXME: This is no longer needed, no Cancel
if (_searchingForBoard) {
_appendStatusLog(tr("Board not found"));
_cancel();
} else {
_appendStatusLog(tr("Bootloader not found"));
_cancel();
}
} else {
Q_ASSERT(false);
}
} }
/// @brief Signals that an error has occured with the specified bootloader commands. Moves the state machine void FirmwareUpgradeController::_status(const QString& statusString)
/// to the appropriate error state.
void FirmwareUpgradeController::_error(const int command, const QString errorString)
{ {
Q_UNUSED(command); _appendStatusLog(statusString);
_appendStatusLog(tr("Error: %1").arg(errorString));
_cancel();
} }
/// @brief Updates the progress bar from long running bootloader commands /// @brief Updates the progress bar from long running bootloader commands
...@@ -604,13 +422,6 @@ void FirmwareUpgradeController::_updateProgress(int curr, int total) ...@@ -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 /// @brief Moves the progress bar ahead on tick while erasing the board
void FirmwareUpgradeController::_eraseProgressTick(void) void FirmwareUpgradeController::_eraseProgressTick(void)
{ {
...@@ -618,33 +429,54 @@ void FirmwareUpgradeController::_eraseProgressTick(void) ...@@ -618,33 +429,54 @@ void FirmwareUpgradeController::_eraseProgressTick(void)
_progressBar->setProperty("value", (float)(_eraseTickCount*_eraseTickMsec) / (float)_eraseTotalMsec); _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 /// 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); Q_ASSERT(_statusLog);
QVariant returnedValue; QVariant returnedValue;
QVariant varText = text; QVariant varText;
if (critical) {
varText = QString("<font color=\"yellow\">%1</font>").arg(text);
} else {
varText = text;
}
QMetaObject::invokeMethod(_statusLog, QMetaObject::invokeMethod(_statusLog,
"append", "append",
Q_RETURN_ARG(QVariant, returnedValue), Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, varText)); Q_ARG(QVariant, varText));
} }
bool FirmwareUpgradeController::activeQGCConnections(void) bool FirmwareUpgradeController::qgcConnections(void)
{ {
return LinkManager::instance()->anyConnectedLinks(); 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 @@ ...@@ -28,6 +28,8 @@
#define FirmwareUpgradeController_H #define FirmwareUpgradeController_H
#include "PX4FirmwareUpgradeThread.h" #include "PX4FirmwareUpgradeThread.h"
#include "LinkManager.h"
#include "FirmwareImage.h"
#include <QObject> #include <QObject>
#include <QUrl> #include <QUrl>
...@@ -51,19 +53,26 @@ public: ...@@ -51,19 +53,26 @@ public:
/// Supported firmware types. If you modify these you will need to update the qml file as well. /// Supported firmware types. If you modify these you will need to update the qml file as well.
typedef enum { typedef enum {
StableFirmware, PX4StableFirmware,
BetaFirmware, PX4BetaFirmware,
DeveloperFirmware, PX4DeveloperFirmware,
CustomFirmware PX4CustomFirmware,
ApmArduCopterQuadFirmware,
ApmArduCopterX8Firmware,
ApmArduCopterHexaFirmware,
ApmArduCopterOctoFirmware,
ApmArduCopterYFirmware,
ApmArduCopterY6Firmware,
ApmArduCopterHeliFirmware,
ApmArduPlaneFirmware,
ApmRoverFirmware,
} FirmwareType_t; } FirmwareType_t;
Q_ENUMS(FirmwareType_t) Q_ENUMS(FirmwareType_t)
/// Firmare type to load Q_PROPERTY(QString boardPort READ boardPort NOTIFY boardFound)
Q_PROPERTY(FirmwareType_t firmwareType READ firmwareType WRITE setFirmwareType) 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 /// TextArea for log output
Q_PROPERTY(QQuickItem* statusLog READ statusLog WRITE setStatusLog) Q_PROPERTY(QQuickItem* statusLog READ statusLog WRITE setStatusLog)
...@@ -71,17 +80,19 @@ public: ...@@ -71,17 +80,19 @@ public:
/// Progress bar for you know what /// Progress bar for you know what
Q_PROPERTY(QQuickItem* progressBar READ progressBar WRITE setProgressBar) Q_PROPERTY(QQuickItem* progressBar READ progressBar WRITE setProgressBar)
Q_INVOKABLE bool activeQGCConnections(void); /// Returns true if there are active QGC connections
Q_INVOKABLE bool pluggedInBoard(void); Q_PROPERTY(bool qgcConnections READ qgcConnections NOTIFY qgcConnectionsChanged)
/// Starts searching for boards on the background thread
Q_INVOKABLE void startBoardSearch(void);
/// Begins the firware upgrade process /// Cancels whatever state the upgrade worker thread is in
Q_INVOKABLE void doFirmwareUpgrade(void); Q_INVOKABLE void cancel(void);
FirmwareType_t firmwareType(void) { return _firmwareType; } /// Called when the firmware type has been selected by the user to continue the flash process.
void setFirmwareType(FirmwareType_t firmwareType) { _firmwareType = firmwareType; } Q_INVOKABLE void flash(FirmwareType_t firmwareType);
QQuickItem* upgradeButton(void) { return _upgradeButton; } // Property accessors
void setUpgradeButton(QQuickItem* upgradeButton) { _upgradeButton = upgradeButton; }
QQuickItem* progressBar(void) { return _progressBar; } QQuickItem* progressBar(void) { return _progressBar; }
void setProgressBar(QQuickItem* progressBar) { _progressBar = progressBar; } void setProgressBar(QQuickItem* progressBar) { _progressBar = progressBar; }
...@@ -89,48 +100,61 @@ public: ...@@ -89,48 +100,61 @@ public:
QQuickItem* statusLog(void) { return _statusLog; } QQuickItem* statusLog(void) { return _statusLog; }
void setStatusLog(QQuickItem* statusLog) { _statusLog = statusLog; } void setStatusLog(QQuickItem* statusLog) { _statusLog = statusLog; }
bool qgcConnections(void);
QString boardPort(void) { return _foundBoardInfo.portName(); }
QString boardDescription(void) { return _foundBoardInfo.description(); }
signals: 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: private slots:
void _downloadProgress(qint64 curr, qint64 total); void _downloadProgress(qint64 curr, qint64 total);
void _downloadFinished(void); void _downloadFinished(void);
void _downloadError(QNetworkReply::NetworkError code); 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 _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 _bootloaderSyncFailed(void);
void _findTimeout(void); void _flashComplete(void);
void _complete(const int command);
void _updateProgress(int curr, int total); void _updateProgress(int curr, int total);
void _restart(void); void _eraseStarted(void);
void _eraseComplete(void);
void _eraseProgressTick(void); void _eraseProgressTick(void);
void _linkDisconnected(LinkInterface* link);
private: private:
void _findBoard(void); void _getFirmwareFile(FirmwareType_t firmwareType);
void _findBootloader(void);
void _cancel(void);
void _getFirmwareFile(void);
void _downloadFirmware(void); void _downloadFirmware(void);
void _erase(void); void _appendStatusLog(const QString& text, bool critical = false);
void _appendStatusLog(const QString& text); void _errorCancel(const QString& msg);
bool _decompressJsonValue(const QJsonObject& jsonObject,
const QByteArray& jsonDocBytes, typedef struct {
const QString& sizeKey, FirmwareType_t firmwareType;
const QString& bytesKey, const char* downloadLocation;
QByteArray& decompressedBytes); } DownloadLocationByFirmwareType_t;
QString _portName; QString _portName;
QString _portDescription; QString _portDescription;
uint32_t _bootloaderVersion;
static const int _boardIDPX4FMUV1 = 5; ///< Board ID for PX4 V1 board /// Information which comes back from the bootloader
static const int _boardIDPX4FMUV2 = 9; ///< Board ID for PX4 V2 board bool _bootloaderFound; ///< true: we have received the foundBootloader signals
static const int _boardIDPX4Flow = 6; ///< Board ID for PX4 Flow board uint32_t _bootloaderVersion; ///< Bootloader version
static const int _boardIDAeroCore = 98; ///< Board ID for Gumstix AeroCore board uint32_t _bootloaderBoardID; ///< Board ID
uint32_t _bootloaderBoardFlashSize; ///< Flash size in bytes of board
bool _startFlashWhenBootloaderFound;
FirmwareType_t _startFlashWhenBootloaderFoundFirmwareType;
uint32_t _boardID; ///< Board ID
uint32_t _boardFlashSize; ///< Flash size in bytes of board
uint32_t _imageSize; ///< Image size of firmware being flashed uint32_t _imageSize; ///< Image size of firmware being flashed
QPixmap _boardIcon; ///< Icon used to display image of board QPixmap _boardIcon; ///< Icon used to display image of board
...@@ -151,12 +175,15 @@ private: ...@@ -151,12 +175,15 @@ private:
static const int _findBoardTimeoutMsec = 30000; ///< Amount of time for user to plug in USB 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 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* _statusLog; ///< Status log TextArea Qml control
QQuickItem* _progressBar; QQuickItem* _progressBar;
bool _searchingForBoard; ///< true: searching for board, false: search for bootloader bool _searchingForBoard; ///< true: searching for board, false: search for bootloader
QSerialPortInfo _foundBoardInfo;
QString _foundBoardType;
FirmwareImage* _image;
}; };
#endif #endif
...@@ -26,20 +26,30 @@ ...@@ -26,20 +26,30 @@
/// @author Don Gagne <don@thegagnes.com> /// @author Don Gagne <don@thegagnes.com>
#include "PX4FirmwareUpgradeThread.h" #include "PX4FirmwareUpgradeThread.h"
#include "PX4Bootloader.h" #include "Bootloader.h"
#include "QGCLoggingCategory.h"
#include "QGC.h"
#include <QTimer> #include <QTimer>
#include <QSerialPortInfo> #include <QSerialPortInfo>
#include <QDebug> #include <QDebug>
#include <QserialPort>
PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(QObject* parent) : PX4FirmwareUpgradeThreadWorker::PX4FirmwareUpgradeThreadWorker(PX4FirmwareUpgradeThreadController* controller) :
QObject(parent), _controller(controller),
_bootloader(NULL), _bootloader(NULL),
_bootloaderPort(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() PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker()
...@@ -52,77 +62,112 @@ PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker() ...@@ -52,77 +62,112 @@ PX4FirmwareUpgradeThreadWorker::~PX4FirmwareUpgradeThreadWorker()
/// @brief Initializes the PX4FirmwareUpgradeThreadWorker with the various child objects which must be created /// @brief Initializes the PX4FirmwareUpgradeThreadWorker with the various child objects which must be created
/// on the worker thread. /// 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 // 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); Q_ASSERT(_timerRetry == NULL);
_timerRetry = new QTimer(this); _timerRetry = new QTimer(this);
Q_CHECK_PTR(_timerRetry); Q_CHECK_PTR(_timerRetry);
_timerRetry->setSingleShot(true); _timerRetry->setSingleShot(true);
_timerRetry->setInterval(_retryTimeout); _timerRetry->setInterval(_retryTimeout);
connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce);
Q_ASSERT(_bootloader == NULL); Q_ASSERT(_bootloader == NULL);
_bootloader = new PX4Bootloader(this); _bootloader = new Bootloader(this);
connect(_bootloader, &PX4Bootloader::updateProgramProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgramProgress); connect(_bootloader, &Bootloader::updateProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgress);
}
void PX4FirmwareUpgradeThreadWorker::_cancel(void)
{
if (_bootloaderPort) {
_bootloaderPort->close();
_bootloaderPort->deleteLater();
_bootloaderPort = NULL;
}
} }
void PX4FirmwareUpgradeThreadWorker::findBoard(int msecTimeout) void PX4FirmwareUpgradeThreadWorker::_startFindBoardLoop(void)
{ {
_foundBoard = false;
_findBoardFirstAttempt = true; _findBoardFirstAttempt = true;
connect(_timerRetry, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce);
_timerTimeout->start(msecTimeout);
_elapsed.start();
_findBoardOnce(); _findBoardOnce();
} }
void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void) void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void)
{ {
qDebug() << "_findBoardOnce"; qCDebug(FirmwareUpgradeLog) << "_findBoardOnce";
QString portName; QSerialPortInfo portInfo;
QString portDescription; PX4FirmwareUpgradeFoundBoardType_t boardType;
foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) { if (_findBoardFromPorts(portInfo, boardType)) {
if (!info.portName().isEmpty() && (info.description().contains("PX4") || info.vendorIdentifier() == 9900 /* 3DR */)) { if (!_foundBoard) {
_foundBoard = true;
qDebug() << "Found Board:"; _foundBoardPortInfo = portInfo;
qDebug() << "\tport name:" << info.portName(); emit foundBoard(_findBoardFirstAttempt, portInfo, boardType);
qDebug() << "\tdescription:" << info.description(); if (!_findBoardFirstAttempt) {
qDebug() << "\tsystem location:" << info.systemLocation(); if (boardType == FoundBoard3drRadio) {
qDebug() << "\tvendor ID:" << info.vendorIdentifier(); _3drRadioForceBootloader(portInfo);
qDebug() << "\tproduct ID:" << info.productIdentifier();
portName = info.systemLocation();
portDescription = info.description();
_closeFind();
emit foundBoard(_findBoardFirstAttempt, portName, portDescription);
return; 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; _findBoardFirstAttempt = false;
emit updateProgress(_elapsed.elapsed(), _timerTimeout->interval());
_timerRetry->start(); _timerRetry->start();
} }
bool PX4FirmwareUpgradeThreadController::pluggedInBoard(void) bool PX4FirmwareUpgradeThreadWorker::_findBoardFromPorts(QSerialPortInfo& portInfo, PX4FirmwareUpgradeFoundBoardType_t& type)
{ {
qDebug() << "pluggedInBoard"; bool found = false;
QString portName;
QString portDescription;
foreach (QSerialPortInfo info, QSerialPortInfo::availablePorts()) { 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; return true;
} }
} }
...@@ -130,143 +175,196 @@ bool PX4FirmwareUpgradeThreadController::pluggedInBoard(void) ...@@ -130,143 +175,196 @@ bool PX4FirmwareUpgradeThreadController::pluggedInBoard(void)
return false; 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);
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
// Once the port shows up, we only try to connect to the bootloader a single time _findBootloader(portInfo, true /* radio mode */, true /* errorOnNotFound */);
_portName = portName;
_findBootloaderOnce();
} }
void PX4FirmwareUpgradeThreadWorker::_findBootloaderOnce(void) bool PX4FirmwareUpgradeThreadWorker::_findBootloader(const QSerialPortInfo& portInfo, bool radioMode, bool errorOnNotFound)
{ {
qDebug() << "_findBootloaderOnce"; qCDebug(FirmwareUpgradeLog) << "_findBootloader";
uint32_t bootloaderVersion = 0;
uint32_t boardID;
uint32_t flashSize = 0;
uint32_t bootloaderVersion, boardID, flashSize;
_bootloaderPort = new QextSerialPort(QextSerialPort::Polling); _bootloaderPort = new QextSerialPort(QextSerialPort::Polling);
Q_CHECK_PTR(_bootloaderPort); Q_CHECK_PTR(_bootloaderPort);
if (_bootloader->open(_bootloaderPort, _portName)) { // 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)) { if (_bootloader->sync(_bootloaderPort)) {
if (_bootloader->getBoardInfo(_bootloaderPort, bootloaderVersion, boardID, flashSize)) { bool success;
_closeFind();
qDebug() << "Found bootloader"; if (radioMode) {
emit foundBootloader(bootloaderVersion, boardID, flashSize); success = _bootloader->get3DRRadioBoardId(_bootloaderPort, boardID);
return; } 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->close();
_bootloaderPort->deleteLater(); _bootloaderPort->deleteLater();
_bootloaderPort = NULL; _bootloaderPort = NULL;
qDebug() << "Bootloader error:" << _bootloader->errorString(); qCDebug(FirmwareUpgradeLog) << "Bootloader error:" << _bootloader->errorString();
emit error(commandBootloader, _bootloader->errorString()); if (errorOnNotFound) {
} emit error(_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) return false;
{
qDebug() << "Find timeout";
_closeFind();
emit findTimeout();
} }
void PX4FirmwareUpgradeThreadWorker::sendBootloaderReboot(void) void PX4FirmwareUpgradeThreadWorker::_reboot(void)
{ {
if (_bootloaderPort) { if (_bootloaderPort) {
if (_bootloaderPort->isOpen()) { if (_bootloaderPort->isOpen()) {
_bootloader->sendBootloaderReboot(_bootloaderPort); _bootloader->reboot(_bootloaderPort);
} }
_bootloaderPort->deleteLater(); _bootloaderPort->deleteLater();
_bootloaderPort = NULL; _bootloaderPort = NULL;
} }
} }
void PX4FirmwareUpgradeThreadWorker::program(const QString firmwareFilename) void PX4FirmwareUpgradeThreadWorker::_flash(void)
{ {
qDebug() << "Program"; qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadWorker::_flash";
if (!_bootloader->program(_bootloaderPort, firmwareFilename)) {
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->deleteLater();
_bootloaderPort = NULL; _bootloaderPort = NULL;
qDebug() << "Program failed:" << _bootloader->errorString(); qCDebug(FirmwareUpgradeLog) << "Program failed:" << _bootloader->errorString();
emit error(commandProgram, _bootloader->errorString()); emit error(_bootloader->errorString());
} else {
qDebug() << "Program complete";
emit complete(commandProgram);
} }
}
void PX4FirmwareUpgradeThreadWorker::verify(const QString firmwareFilename) emit status("Verifying program...");
{
qDebug() << "Verify"; if (_bootloader->verify(_bootloaderPort, _controller->image())) {
if (!_bootloader->verify(_bootloaderPort, firmwareFilename)) { qCDebug(FirmwareUpgradeLog) << "Verify complete";
qDebug() << "Verify failed:" << _bootloader->errorString(); emit status("Verify complete");
emit error(commandVerify, _bootloader->errorString());
} else { } else {
qDebug() << "Verify complete"; qCDebug(FirmwareUpgradeLog) << "Verify failed:" << _bootloader->errorString();
emit complete(commandVerify); emit error(_bootloader->errorString());
} }
_bootloaderPort->deleteLater(); }
_bootloaderPort = NULL;
emit _reboot();
emit flashComplete();
} }
void PX4FirmwareUpgradeThreadWorker::erase(void) bool PX4FirmwareUpgradeThreadWorker::_erase(void)
{ {
qDebug() << "Erase"; qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadWorker::_erase";
if (!_bootloader->erase(_bootloaderPort)) {
_bootloaderPort->deleteLater(); emit eraseStarted();
_bootloaderPort = NULL; emit status("Erasing previous program...");
qDebug() << "Erase failed:" << _bootloader->errorString();
emit error(commandErase, _bootloader->errorString()); if (_bootloader->erase(_bootloaderPort)) {
qCDebug(FirmwareUpgradeLog) << "Erase complete";
emit status("Erase complete");
emit eraseComplete();
return true;
} else { } else {
qDebug() << "Erase complete"; qCDebug(FirmwareUpgradeLog) << "Erase failed:" << _bootloader->errorString();
emit complete(commandErase); emit error(_bootloader->errorString());
return false;
} }
} }
PX4FirmwareUpgradeThreadController::PX4FirmwareUpgradeThreadController(QObject* parent) : PX4FirmwareUpgradeThreadController::PX4FirmwareUpgradeThreadController(QObject* parent) :
QObject(parent) QObject(parent)
{ {
_worker = new PX4FirmwareUpgradeThreadWorker(); _worker = new PX4FirmwareUpgradeThreadWorker(this);
Q_CHECK_PTR(_worker); Q_CHECK_PTR(_worker);
_workerThread = new QThread(this); _workerThread = new QThread(this);
Q_CHECK_PTR(_workerThread); Q_CHECK_PTR(_workerThread);
_worker->moveToThread(_workerThread); _worker->moveToThread(_workerThread);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoard, this, &PX4FirmwareUpgradeThreadController::_foundBoard); 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::foundBootloader, this, &PX4FirmwareUpgradeThreadController::_foundBootloader);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::bootloaderSyncFailed, this, &PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed); connect(_worker, &PX4FirmwareUpgradeThreadWorker::bootloaderSyncFailed, this, &PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error); connect(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::complete, this, &PX4FirmwareUpgradeThreadController::_complete); connect(_worker, &PX4FirmwareUpgradeThreadWorker::status, this, &PX4FirmwareUpgradeThreadController::_status);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::findTimeout, this, &PX4FirmwareUpgradeThreadController::_findTimeout); connect(_worker, &PX4FirmwareUpgradeThreadWorker::eraseStarted, this, &PX4FirmwareUpgradeThreadController::_eraseStarted);
connect(_worker, &PX4FirmwareUpgradeThreadWorker::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress); 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(); _workerThread->start();
...@@ -277,36 +375,24 @@ PX4FirmwareUpgradeThreadController::~PX4FirmwareUpgradeThreadController() ...@@ -277,36 +375,24 @@ PX4FirmwareUpgradeThreadController::~PX4FirmwareUpgradeThreadController()
{ {
_workerThread->quit(); _workerThread->quit();
_workerThread->wait(); _workerThread->wait();
}
void PX4FirmwareUpgradeThreadController::findBoard(int msecTimeout) delete _workerThread;
{
qDebug() << "PX4FirmwareUpgradeThreadController::findBoard";
emit _findBoardOnThread(msecTimeout);
} }
void PX4FirmwareUpgradeThreadController::findBootloader(const QString& portName, int msecTimeout) void PX4FirmwareUpgradeThreadController::startFindBoardLoop(void)
{ {
qDebug() << "PX4FirmwareUpgradeThreadController::findBootloader"; qCDebug(FirmwareUpgradeLog) << "PX4FirmwareUpgradeThreadController::findBoard";
emit _findBootloaderOnThread(portName, msecTimeout); emit _startFindBoardLoopOnThread();
}
void PX4FirmwareUpgradeThreadController::_foundBoard(bool firstTry, const QString portName, QString portDescription)
{
emit foundBoard(firstTry, portName, portDescription);
}
void PX4FirmwareUpgradeThreadController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize)
{
emit foundBootloader(bootloaderVersion, boardID, flashSize);
} }
void PX4FirmwareUpgradeThreadController::_bootloaderSyncFailed(void) void PX4FirmwareUpgradeThreadController::cancel(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 @@ ...@@ -28,16 +28,27 @@
#ifndef PX4FirmwareUpgradeThread_H #ifndef PX4FirmwareUpgradeThread_H
#define PX4FirmwareUpgradeThread_H #define PX4FirmwareUpgradeThread_H
#include "Bootloader.h"
#include "FirmwareImage.h"
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include <QTime> #include <QTime>
#include <QSerialPortInfo>
#include "qextserialport.h" #include "qextserialport.h"
#include <stdint.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 /// @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 /// internally by the PX4FirmwareUpgradeThreadController. Clients should call the various public methods
...@@ -47,52 +58,59 @@ class PX4FirmwareUpgradeThreadWorker : public QObject ...@@ -47,52 +58,59 @@ class PX4FirmwareUpgradeThreadWorker : public QObject
Q_OBJECT Q_OBJECT
public: public:
PX4FirmwareUpgradeThreadWorker(QObject* parent = NULL); PX4FirmwareUpgradeThreadWorker(PX4FirmwareUpgradeThreadController* controller);
~PX4FirmwareUpgradeThreadWorker(); ~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: 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 foundBootloader(int bootloaderVersion, int boardID, int flashSize);
void bootloaderSyncFailed(void); void bootloaderSyncFailed(void);
void error(const int command, const QString errorString); void error(const QString& errorString);
void complete(const int command); void status(const QString& statusText);
void findTimeout(void); void eraseStarted(void);
void updateProgress(int curr, int total); void eraseComplete(void);
void flashComplete(void);
private slots: private slots:
void _init(void);
void _startFindBoardLoop(void);
void _reboot(void);
void _flash(void);
void _findBoardOnce(void); void _findBoardOnce(void);
void _findBootloaderOnce(void); void _updateProgress(int curr, int total) { emit updateProgress(curr, total); }
void _updateProgramProgress(int curr, int total) { emit updateProgress(curr, total); } void _cancel(void);
void _closeFind(void);
private: 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; QextSerialPort* _bootloaderPort;
QTimer* _timerTimeout;
QTimer* _timerRetry; QTimer* _timerRetry;
QTime _elapsed; QTime _elapsed;
QString _portName;
static const int _retryTimeout = 1000; 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 /// @brief Provides methods to interact with the bootloader. The commands themselves are signalled
...@@ -105,82 +123,74 @@ public: ...@@ -105,82 +123,74 @@ public:
PX4FirmwareUpgradeThreadController(QObject* parent = NULL); PX4FirmwareUpgradeThreadController(QObject* parent = NULL);
~PX4FirmwareUpgradeThreadController(void); ~PX4FirmwareUpgradeThreadController(void);
/// Returns true is a board is currently connected via USB /// @brief Begins the process of searching for a supported board connected to any serial port. This will
bool pluggedInBoard(void); /// continue until cancelFind is called. Signals foundBoard and boardGone as boards come and go.
void startFindBoardLoop(void);
/// @brief Begins the process of searching for a PX4 board connected to any serial port.
/// @param msecTimeout Numbers of msecs to continue looking for a board to become available.
void findBoard(int msecTimeout);
/// @brief Begins the process of attempting to communicate with the bootloader on the specified port.
/// @param portName Name of port to attempt a bootloader connection on.
/// @param msecTimeout Number of msecs to continue to wait for a bootloader to appear on the port.
void findBootloader(const QString& portName, int msecTimeout);
/// @brief Cancel an in progress findBoard or FindBootloader void cancel(void);
void cancelFind(void) { emit _cancelFindOnThread(); }
/// @brief Sends a reboot command to the bootloader /// @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 flash(const FirmwareImage* image);
void program(const QString firmwareFilename) { emit _programOnThread(firmwareFilename); }
/// @brief Verify the board flash with respect to the specified firmware image const FirmwareImage* image(void) { return _image; }
void verify(const QString firmwareFilename) { emit _verifyOnThread(firmwareFilename); }
/// @brief Send and erase command to the bootloader
void erase(void) { emit _eraseOnThread(); }
signals: signals:
/// @brief Emitted by the findBoard process when it finds the board. /// @brief Emitted by the find board process when it finds a board.
/// @param firstTry true: board found on first attempt void foundBoard(bool firstAttempt, const QSerialPortInfo &portInfo, int type);
/// @param portName Port that board is on
/// @param portDescription User friendly port description void noBoardFound(void);
void foundBoard(bool firstTry, const QString portname, QString portDescription);
/// @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 /// @brief Emitted by the findBootloader process when has a connection to the bootloader
void foundBootloader(int bootloaderVersion, int boardID, int flashSize); void foundBootloader(int bootloaderVersion, int boardID, int flashSize);
/// @brief Emitted by the bootloader commands when an error occurs. /// @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 QString& errorString);
void error(const int errorCommand, const QString errorString);
void status(const QString& status);
/// @brief Signalled when the findBootloader process connects to the port, but cannot sync to the /// @brief Signalled when the findBootloader process connects to the port, but cannot sync to the
/// bootloader. /// bootloader.
void bootloaderSyncFailed(void); void bootloaderSyncFailed(void);
/// @brief Signalled when the findBoard or findBootloader process times out before success void eraseStarted(void);
void findTimeout(void); void eraseComplete(void);
/// @brief Signalled by the bootloader commands other than find* that they have complete successfully. void flashComplete(void);
/// @param command Command which completed, using PX4FirmwareUpgradeThreadWorker command* enum values
void complete(const int command);
/// @brief Signalled to update progress for long running bootloader commands /// @brief Signalled to update progress for long running bootloader commands
void updateProgress(int curr, int total); void updateProgress(int curr, int total);
// Internal signals to communicate with thread worker
void _initThreadWorker(void); void _initThreadWorker(void);
void _findBoardOnThread(int msecTimeout); void _startFindBoardLoopOnThread(void);
void _findBootloaderOnThread(const QString& portName, int msecTimeout); void _rebootOnThread(void);
void _sendBootloaderRebootOnThread(void); void _flashOnThread(void);
void _programOnThread(const QString firmwareFilename); void _cancel(void);
void _verifyOnThread(const QString firmwareFilename);
void _eraseOnThread(void);
void _cancelFindOnThread(void);
private slots: private slots:
void _foundBoard(bool firstTry, const QString portname, QString portDescription); void _foundBoard(bool firstAttempt, const QSerialPortInfo& portInfo, int type) { emit foundBoard(firstAttempt, portInfo, type); }
void _foundBootloader(int bootloaderVersion, int boardID, int flashSize); void _noBoardFound(void) { emit noBoardFound(); }
void _bootloaderSyncFailed(void); void _boardGone(void) { emit boardGone(); }
void _error(const int errorCommand, const QString errorString) { emit error(errorCommand, errorString); } void _foundBootloader(int bootloaderVersion, int boardID, int flashSize) { emit foundBootloader(bootloaderVersion, boardID, flashSize); }
void _complete(const int command) { emit complete(command); } void _bootloaderSyncFailed(void) { emit bootloaderSyncFailed(); }
void _findTimeout(void); void _error(const QString& errorString) { emit error(errorString); }
void _updateProgress(int curr, int total) { emit updateProgress(curr, total); } void _status(const QString& statusText) { emit status(statusText); }
void _eraseStarted(void) { emit eraseStarted(); }
void _eraseComplete(void) { emit eraseComplete(); }
void _flashComplete(void) { emit flashComplete(); }
private: private:
void _updateProgress(int curr, int total) { emit updateProgress(curr, total); }
PX4FirmwareUpgradeThreadWorker* _worker; PX4FirmwareUpgradeThreadWorker* _worker;
QThread* _workerThread; ///< Thread which PX4FirmwareUpgradeThreadWorker runs on QThread* _workerThread; ///< Thread which PX4FirmwareUpgradeThreadWorker runs on
const FirmwareImage* _image;
}; };
#endif #endif
...@@ -30,6 +30,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -30,6 +30,7 @@ This file is part of the QGROUNDCONTROL project
#include <QApplication> #include <QApplication>
#include <QSslSocket> #include <QSslSocket>
#include <QSerialPortInfo>
#include "QGCApplication.h" #include "QGCApplication.h"
#include "MainWindow.h" #include "MainWindow.h"
...@@ -49,6 +50,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -49,6 +50,7 @@ This file is part of the QGROUNDCONTROL project
#undef main #undef main
#endif #endif
Q_DECLARE_METATYPE(QSerialPortInfo)
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
...@@ -107,6 +109,8 @@ int main(int argc, char *argv[]) ...@@ -107,6 +109,8 @@ int main(int argc, char *argv[])
qRegisterMetaType<QSerialPort::SerialPortError>(); qRegisterMetaType<QSerialPort::SerialPortError>();
#endif #endif
qRegisterMetaType<QAbstractSocket::SocketError>(); qRegisterMetaType<QAbstractSocket::SocketError>();
qRegisterMetaType<QSerialPortInfo>();
// We statically link to the google QtLocation plugin // We statically link to the google QtLocation plugin
#ifdef Q_OS_WIN #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