Files
edgetx-gx12/companion/src/flash/flashfirmwaredialog.cpp
T

404 lines
12 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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.
*/
#include "flashfirmwaredialog.h"
#include "ui_flashfirmwaredialog.h"
#include "appdata.h"
#include "helpers.h"
#include "radiointerface.h"
#include "firmwareinterface.h"
#include "progressdialog.h"
#include "progresswidget.h"
#include "splashlibrarydialog.h"
#include "storage.h"
FlashFirmwareDialog::FlashFirmwareDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::FlashFirmwareDialog),
fwName(g.profile[g.id()].fwName())
{
ui->setupUi(this);
if (!g.currentProfile().splashFile().isEmpty()){
imageSource = PROFILE;
imageFile = g.currentProfile().splashFile();
ui->useProfileSplash->setChecked(true);
} else {
imageSource = FIRMWARE;
imageFile = "";
ui->useProfileSplash->setDisabled(true);
}
ui->checkHardwareCompatibility->setChecked(g.checkHardwareCompatibility());
ui->checkBackup->setChecked(g.backupOnFlash());
detectClicked(true);
updateUI();
connect(ui->detectButton, &QPushButton::clicked, this, &FlashFirmwareDialog::detectClicked);
connect(ui->loadButton, &QPushButton::clicked, this, &FlashFirmwareDialog::loadClicked);
connect(ui->useProfileSplash, &QRadioButton::clicked, this, &FlashFirmwareDialog::useProfileSplashClicked);
connect(ui->useFirmwareSplash, &QRadioButton::clicked, this, &FlashFirmwareDialog::useFirmwareSplashClicked);
connect(ui->useLibrarySplash, &QRadioButton::clicked, this, &FlashFirmwareDialog::useLibrarySplashClicked);
connect(ui->useExternalSplash, &QRadioButton::clicked, this, &FlashFirmwareDialog::useExternalSplashClicked);
connect(ui->writeButton, &QPushButton::clicked, this, &FlashFirmwareDialog::writeButtonClicked);
connect(ui->cancelButton, &QPushButton::clicked, [this]() { close(); } );
}
FlashFirmwareDialog::~FlashFirmwareDialog()
{
delete ui;
}
void FlashFirmwareDialog::updateUI()
{
ui->firmwareFilename->setText(fwName);
if (connectionMode) {
ui->loadButton->setEnabled(true);
ui->writeButton->setEnabled(isFileConnectionCompatible() && QFile(fwName).exists());
} else {
ui->loadButton->setEnabled(false);
ui->writeButton->setEnabled(false);
}
FirmwareInterface firmware(fwName);
if (firmware.isValid()) {
ui->firmwareInfoFrame->show();
ui->date->setText(firmware.getDate() + " " + firmware.getTime());
ui->version->setText(firmware.getVersion());
ui->variant->setText(firmware.getFlavour());
ui->date->setEnabled(true);
ui->version->setEnabled(true);
ui->variant->setEnabled(true);
if (firmware.hasSplash()) {
ui->splashFrame->show();
ui->splash->setFixedSize(firmware.getSplashWidth(), firmware.getSplashHeight());
} else {
ui->splashFrame->hide();
}
} else {
imageSource = FIRMWARE;
ui->firmwareInfoFrame->hide();
ui->splashFrame->hide();
}
QImage image;
switch (imageSource) {
case FIRMWARE:
ui->useFirmwareSplash->setChecked(true);
image = firmware.getSplash();
break;
case PROFILE:
ui->useProfileSplash->setChecked(true);
image.load(g.currentProfile().splashFile());
break;
case LIBRARY:
ui->useLibrarySplash->setChecked(true);
image.load(imageFile);
break;
case EXTERNAL:
ui->useExternalSplash->setChecked(true);
image.load(imageFile);
break;
}
if (!image.isNull())
ui->splash->setPixmap(makePixMap(image));
QTimer::singleShot(0, [=]() { adjustSize(); });
}
void FlashFirmwareDialog::loadClicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Firmware File"),
g.flashDir(),
getFirmwareFilesFilter());
if (!fileName.isEmpty()) {
if (!firmwareFileExtensions().contains(QFileInfo(fileName).suffix().toLower())) {
QMessageBox::critical(this, tr("Open Firmware File"),
tr("%1 has an unsupported file extension")
.arg(fileName));
return;
}
FirmwareInterface fw(fileName);
if (!fw.isValid()) {
QMessageBox::critical(this, tr("Open Firmware File"),
tr("%1 is not a valid firmware file")
.arg(fileName));
return;
}
if (connectionMode == CONNECTION_UF2) {
const Uf2Info uf2(getUf2Info());
if (fw.getFlavour() != uf2.board) {
QMessageBox::critical(this, tr("Open Firmware File"),
tr("%1 \nFirmware file is for %2 radio however connected radio is %3")
.arg(fileName)
.arg(fw.getFlavour())
.arg(uf2.board));
return;
}
}
if (fw.getFlavour() != getCurrentFirmware()->getFlavour()) {
QMessageBox::critical(this, tr("Open Firmware File"),
tr("%1 \nFirmware file is for %2 radio however profile is for %3 radio")
.arg(fileName)
.arg(fw.getFlavour())
.arg(getCurrentFirmware()->getFlavour()));
return;
}
fwName = fileName;
updateUI();
}
}
void FlashFirmwareDialog::useFirmwareSplashClicked()
{
FirmwareInterface firmware(fwName);
if (!firmware.isValid()) {
QMessageBox::warning(this, CPN_STR_TTL_ERROR, tr( "The firmware file is not valid." ));
} else if (!firmware.hasSplash()) {
QMessageBox::warning(this, CPN_STR_TTL_ERROR,
tr( "There is no start screen image in the firmware file." ));
} else {
imageSource = FIRMWARE;
}
updateUI();
}
void FlashFirmwareDialog::useProfileSplashClicked()
{
QString fileName = g.currentProfile().splashFile();
if (!fileName.isEmpty()) {
QImage image(fileName);
if (image.isNull()) {
QMessageBox::critical(this, CPN_STR_TTL_ERROR,
tr("Profile image %1 is invalid.").arg(fileName));
} else {
imageSource = PROFILE;
}
}
updateUI();
}
void FlashFirmwareDialog::useExternalSplashClicked()
{
QString supportedImageFormats;
for (int formatIndex = 0; formatIndex < QImageReader::supportedImageFormats().count(); formatIndex++) {
supportedImageFormats += QLatin1String(" *.") + QImageReader::supportedImageFormats()[formatIndex];
}
QString fileName = QFileDialog::getOpenFileName(this, tr("Open image file to use as radio start screen"),
g.imagesDir(),
tr("Images (%1)").arg(supportedImageFormats));
if (!fileName.isEmpty()){
g.imagesDir( QFileInfo(fileName).dir().absolutePath() );
QImage image(fileName);
if (image.isNull()) {
QMessageBox::critical(this, CPN_STR_TTL_ERROR,
tr("Image could not be loaded from %1")
.arg(fileName));
} else {
imageSource = EXTERNAL;
imageFile = fileName;
}
}
updateUI();
}
void FlashFirmwareDialog::useLibrarySplashClicked()
{
QString fileName;
SplashLibraryDialog(this, &fileName).exec();
if (!fileName.isEmpty()) {
QImage image(fileName);
if (image.isNull()) {
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("The library image could not be loaded"));
} else {
imageSource = LIBRARY;
imageFile = fileName;
}
}
updateUI();
}
void FlashFirmwareDialog::writeButtonClicked()
{
g.flashDir(QFileInfo(fwName).dir().absolutePath());
g.currentProfile().fwName(fwName);
g.checkHardwareCompatibility(ui->checkHardwareCompatibility->isChecked());
g.backupOnFlash(ui->checkBackup->isChecked());
if (imageSource != FIRMWARE) {
// load the splash image
const QPixmap pixmap = ui->splash->pixmap(Qt::ReturnByValue);
QImage image;
image = pixmap.toImage().scaled(ui->splash->width(), ui->splash->height());
if (image.isNull()) {
QMessageBox::critical(this, CPN_STR_TTL_WARNING, tr("Splash image not found"));
return;
}
// write the customized firmware
QString tempFile;
tempFile = generateProcessUniqueTempFileName("flash.bin");
qDebug() << "patching" << fwName << "with custom splash screen and saving to" << tempFile;
FirmwareInterface firmware(fwName);
firmware.setSplash(image);
if (firmware.save(tempFile) <= 0) {
QMessageBox::critical(this, CPN_STR_TTL_WARNING, tr("Cannot save customised firmware"));
return;
}
startWrite(tempFile);
} else {
startWrite(fwName);
}
}
void FlashFirmwareDialog::startWrite(const QString &filename)
{
close();
ProgressDialog progressDialog(this, tr("Flash Firmware to Radio"),
CompanionIcon("write_flash.png"));
auto progress = progressDialog.progress();
progress->addMessage(tr("Flashing with file: %1").arg(filename));
bool checkHw = g.checkHardwareCompatibility();
bool backup = g.backupOnFlash();
FirmwareInterface newfw(filename);
if (g.checkHardwareCompatibility() || backup) {
readFirmware(
[this, &newfw, progress, checkHw, backup](const QByteArray &_data) {
FirmwareInterface currfw(_data);
if (!currfw.isValid()) {
QString errMsg(tr("Firmware read from radio invalid"));
progress->addMessage(errMsg, QtFatalMsg);
progress->setInfo(errMsg);
return;
}
if (backup) {
if (!writeFirmwareToFile(this, _data, progress)) {
return;
}
} else {
progress->addMessage(tr("Backup of old firmware not requested"));
}
if (checkHw) {
if (!newfw.isHardwareCompatible(currfw)) {
QString errMsg(tr("New firmware is not compatible"));
progress->addMessage(errMsg, QtFatalMsg);
progress->setInfo(errMsg);
return;
} else {
progress->addMessage(tr("New firmware is compatible"));
}
} else {
progress->addMessage(tr("New firmware compatibity check not requested"));
}
progress->addMessage(tr("Start flashing firmware"));
writeFirmware(newfw.getFlash(), progress);
},
[this, progress](const QString &err) {
progress->addMessage(tr("Could not read current firmware: %1").arg(err));
},
progress);
} else {
progress->addMessage(tr("Start flashing firmware (no backup or compatiblity check)"));
writeFirmware(newfw.getFlash(), progress);
}
progressDialog.exec();
if (isTempFileName(filename)) {
qDebug() << "Removing temporary file" << filename;
qunlink(filename);
}
}
void FlashFirmwareDialog::detectClicked(bool atLoad)
{
QString connectionMsg;
if (isUf2DeviceFound()) {
connectionMode = CONNECTION_UF2;
connectionMsg = tr("Radio connection mode: UF2");
} else if (isDfuDeviceFound()) {
connectionMode = CONNECTION_DFU;
connectionMsg = tr("Radio connection mode: DFU");
} else {
connectionMode = CONNECTION_NONE;
connectionMsg = tr("ALERT: No radio connected");
}
ui->connectionMode->setText(connectionMsg);
if (!connectionMode) {
QMessageBox::warning((atLoad ? (QWidget *)parent(): this), tr("Detect Radio"),
tr("Radio could not be detected by DFU or UF2 modes") % ".\n" %
tr("Check cable is securely connected and radio lights are illuminated") % ".\n" %
tr("Note: USB mode is not suitable for flashing firmware."));
}
if (!isFileConnectionCompatible())
fwName.clear();
if (!atLoad)
updateUI();
}
bool FlashFirmwareDialog::isFileConnectionCompatible()
{
if (!fwName.isEmpty()) {
const QString extn(QFileInfo(fwName).suffix().toLower());
if ((connectionMode == CONNECTION_DFU && dfuFileExtensions().contains(extn)) ||
(connectionMode == CONNECTION_UF2 && uf2FileExtensions().contains(extn))) {
return true;
}
}
return false;
}