FirmwareUpgradeController.cc 19.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*=====================================================================
 
 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 PX4 Firmware Upgrade UI
///     @author Don Gagne <don@thegagnes.com>

#include "FirmwareUpgradeController.h"
29
#include "Bootloader.h"
30 31 32 33 34 35 36
#include "QGCFileDialog.h"
#include "QGCMessageBox.h"

/// @Brief Constructs a new FirmwareUpgradeController Widget. This widget is used within the PX4VehicleConfig set of screens.
FirmwareUpgradeController::FirmwareUpgradeController(void) :
    _downloadManager(NULL),
    _downloadNetworkReply(NULL),
37 38
    _statusLog(NULL),
    _image(NULL)
39 40 41 42
{
    _threadController = new PX4FirmwareUpgradeThreadController(this);
    Q_CHECK_PTR(_threadController);

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
    connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard,             this, &FirmwareUpgradeController::_foundBoard);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::noBoardFound,           this, &FirmwareUpgradeController::_noBoardFound);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::boardGone,              this, &FirmwareUpgradeController::_boardGone);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBootloader,        this, &FirmwareUpgradeController::_foundBootloader);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::bootloaderSyncFailed,   this, &FirmwareUpgradeController::_bootloaderSyncFailed);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::error,                  this, &FirmwareUpgradeController::_error);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress,         this, &FirmwareUpgradeController::_updateProgress);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::status,                 this, &FirmwareUpgradeController::_status);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseStarted,           this, &FirmwareUpgradeController::_eraseStarted);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseComplete,          this, &FirmwareUpgradeController::_eraseComplete);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::flashComplete,          this, &FirmwareUpgradeController::_flashComplete);
    connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress,         this, &FirmwareUpgradeController::_updateProgress);
    
    connect(LinkManager::instance(), &LinkManager::linkDisconnected, this, &FirmwareUpgradeController::_linkDisconnected);

58 59 60
    connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
}

61
void FirmwareUpgradeController::startBoardSearch(void)
62
{
63 64 65
    _bootloaderFound = false;
    _startFlashWhenBootloaderFound = false;
    _threadController->startFindBoardLoop();
66 67
}

68
void FirmwareUpgradeController::flash(FirmwareType_t firmwareType)
69
{
70 71 72 73 74 75 76
    if (_bootloaderFound) {
        _getFirmwareFile(firmwareType);
    } else {
        // We haven't found the bootloader yet. Need to wait until then to flash
        _startFlashWhenBootloaderFound = true;
        _startFlashWhenBootloaderFoundFirmwareType = firmwareType;
    }
77 78
}

79
void FirmwareUpgradeController::cancel(void)
80
{
81 82 83
    _eraseTimer.stop();
    _threadController->cancel();
}
84

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int type)
{
    _foundBoardInfo = info;
    switch (type) {
        case FoundBoardPX4FMUV1:
            _foundBoardType = "PX4 FMU V1";
            _startFlashWhenBootloaderFound = false;
            break;
        case FoundBoardPX4FMUV2:
            _foundBoardType = "Pixhawk";
            _startFlashWhenBootloaderFound = false;
            break;
        case FoundBoardPX4Flow:
        case FoundBoard3drRadio:
            _foundBoardType = type == FoundBoardPX4Flow ? "PX4 Flow" : "3DR Radio";
            if (!firstAttempt) {
                // PX4 Flow and Radio always flash stable firmware, so we can start right away without
                // any further user input.
                _startFlashWhenBootloaderFound = true;
                _startFlashWhenBootloaderFoundFirmwareType = PX4StableFirmware;
            }
            break;
107
    }
108 109 110
    
    qCDebug(FirmwareUpgradeLog) << _foundBoardType;
    emit boardFound();
111 112
}

113 114

void FirmwareUpgradeController::_noBoardFound(void)
115
{
116 117 118 119 120 121
    emit noBoardFound();
}

void FirmwareUpgradeController::_boardGone(void)
{
    emit boardGone();
122 123 124 125 126 127
}

/// @brief Called when the bootloader is connected to by the findBootloader process. Moves the state machine
///         to the next step.
void FirmwareUpgradeController::_foundBootloader(int bootloaderVersion, int boardID, int flashSize)
{
128
    _bootloaderFound = true;
129
    _bootloaderVersion = bootloaderVersion;
130 131
    _bootloaderBoardID = boardID;
    _bootloaderBoardFlashSize = flashSize;
132
    
133 134 135 136
    _appendStatusLog("Connected to bootloader:");
    _appendStatusLog(QString("  Version: %1").arg(_bootloaderVersion));
    _appendStatusLog(QString("  Board ID: %1").arg(_bootloaderBoardID));
    _appendStatusLog(QString("  Flash size: %1").arg(_bootloaderBoardFlashSize));
137
    
138 139 140
    if (_startFlashWhenBootloaderFound) {
        flash(_startFlashWhenBootloaderFoundFirmwareType);
    }
141 142 143 144 145 146
}

/// @brief Called when the findBootloader process is unable to sync to the bootloader. Moves the state
///         machine to the appropriate error state.
void FirmwareUpgradeController::_bootloaderSyncFailed(void)
{
147
    _errorCancel("Unable to sync with bootloader.");
148 149 150
}

/// @brief Prompts the user to select a firmware file if needed and moves the state machine to the next state.
151
void FirmwareUpgradeController::_getFirmwareFile(FirmwareType_t firmwareType)
152
{
153 154 155 156 157 158 159 160 161 162 163 164 165
    static DownloadLocationByFirmwareType_t rgPX4FMUV2Firmware[] = {
        { PX4StableFirmware,            "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v2_default.px4" },
        { PX4BetaFirmware,              "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v2_default.px4" },
        { PX4DeveloperFirmware,         "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v2_default.px4"},
        { ApmArduCopterQuadFirmware,    "http://firmware.diydrones.com/Copter/stable/PX4-quad/ArduCopter-v2.px4" },
        { ApmArduCopterX8Firmware,      "http://firmware.diydrones.com/Copter/stable/PX4-octa-quad/ArduCopter-v2.px4" },
        { ApmArduCopterHexaFirmware,    "http://firmware.diydrones.com/Copter/stable/PX4-hexa/ArduCopter-v2.px4" },
        { ApmArduCopterOctoFirmware,    "http://firmware.diydrones.com/Copter/stable/PX4-octa/ArduCopter-v2.px4" },
        { ApmArduCopterYFirmware,       "http://firmware.diydrones.com/Copter/stable/PX4-tri/ArduCopter-v2.px4" },
        { ApmArduCopterY6Firmware,      "http://firmware.diydrones.com/Copter/stable/PX4-y6/ArduCopter-v2.px4" },
        { ApmArduCopterHeliFirmware,    "http://firmware.diydrones.com/Copter/stable/PX4-heli/ArduCopter-v2.px4" },
        { ApmArduPlaneFirmware,         "http://firmware.diydrones.com/Plane/stable/PX4/ArduPlane-v2.px4" },
        { ApmRoverFirmware,             "http://firmware.diydrones.com/Plane/stable/PX4/APMrover2-v2.px4" },
166
    };
167
    static const size_t crgPX4FMUV2Firmware = sizeof(rgPX4FMUV2Firmware) / sizeof(rgPX4FMUV2Firmware[0]);
168
    
169 170 171 172
    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" },
173
    };
174 175 176 177 178 179
    static const size_t crgAeroCoreFirmware = sizeof(rgAeroCoreFirmware) / sizeof(rgAeroCoreFirmware[0]);
    
    static const DownloadLocationByFirmwareType_t rgPX4FMUV1Firmware[] = {
        { PX4StableFirmware,    "http://px4-travis.s3.amazonaws.com/Firmware/stable/px4fmu-v1_default.px4" },
        { PX4BetaFirmware,      "http://px4-travis.s3.amazonaws.com/Firmware/beta/px4fmu-v1_default.px4" },
        { PX4DeveloperFirmware, "http://px4-travis.s3.amazonaws.com/Firmware/master/px4fmu-v1_default.px4" },
180
    };
181 182 183 184
    static const size_t crgPX4FMUV1Firmware = sizeof(rgPX4FMUV1Firmware) / sizeof(rgPX4FMUV1Firmware[0]);
    
    static const DownloadLocationByFirmwareType_t rgPX4FlowFirmware[] = {
        { PX4StableFirmware, "http://px4-travis.s3.amazonaws.com/Flow/master/px4flow.px4" },
185
    };
186
    static const size_t crgPX4FlowFirmware = sizeof(rgPX4FlowFirmware) / sizeof(rgPX4FlowFirmware[0]);
187
    
188 189 190 191 192 193
    static const DownloadLocationByFirmwareType_t rg3DRRadioFirmware[] = {
        { PX4StableFirmware, "http://firmware.diydrones.com/SiK/stable/radio~hm_trp.ihx" },
    };
    static const size_t crg3DRRadioFirmware = sizeof(rg3DRRadioFirmware) / sizeof(rg3DRRadioFirmware[0]);
    
    // Select the firmware set based on board type
194
    
195 196 197 198 199
    const DownloadLocationByFirmwareType_t* prgFirmware;
    size_t crgFirmware;
    
    switch (_bootloaderBoardID) {
        case Bootloader::boardIDPX4FMUV1:
200
            prgFirmware = rgPX4FMUV1Firmware;
201
            crgFirmware = crgPX4FMUV1Firmware;
202 203
            break;
            
204
        case Bootloader::boardIDPX4Flow:
205
            prgFirmware = rgPX4FlowFirmware;
206
            crgFirmware = crgPX4FlowFirmware;
207 208
            break;
            
209
        case Bootloader::boardIDPX4FMUV2:
210
            prgFirmware = rgPX4FMUV2Firmware;
211
            crgFirmware = crgPX4FMUV2Firmware;
212
            break;
213 214
            
        case Bootloader::boardIDAeroCore:
215
            prgFirmware = rgAeroCoreFirmware;
216
            crgFirmware = crgAeroCoreFirmware;
217
            break;
218 219 220 221 222 223
            
        case Bootloader::boardID3DRRadio:
            prgFirmware = rg3DRRadioFirmware;
            crgFirmware = crg3DRRadioFirmware;
            break;
            
224 225 226 227
        default:
            prgFirmware = NULL;
            break;
    }
228 229 230 231
    
    if (prgFirmware == NULL && firmwareType != PX4CustomFirmware) {
        _errorCancel("Attempting to flash an unknown board type, you must select 'Custom firmware file'");
        return;
232 233
    }
    
234
    if (firmwareType == PX4CustomFirmware) {
235
        _firmwareFilename = QGCFileDialog::getOpenFileName(NULL,                                                                // Parent to main window
236
                                                           "Select Firmware File",                                              // Dialog Caption
237
                                                           QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), // Initial directory
238
                                                           "Firmware Files (*.px4 *.bin *.ihx)");                               // File filter
239
    } else {
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
        bool found = false;
        
        for (size_t i=0; i<crgFirmware; i++) {
            if (prgFirmware->firmwareType == firmwareType) {
                found = true;
                break;
            }
            prgFirmware++;
        }
        
        if (found) {
            _firmwareFilename = prgFirmware->downloadLocation;
        } else {
            _errorCancel("Unable to find specified firmware download location");
            return;
        }
256 257 258
    }
    
    if (_firmwareFilename.isEmpty()) {
259
        _errorCancel("No firmware file selected");
260 261 262 263 264 265 266 267 268 269
    } else {
        _downloadFirmware();
    }
}

/// @brief Begins the process of downloading the selected firmware file.
void FirmwareUpgradeController::_downloadFirmware(void)
{
    Q_ASSERT(!_firmwareFilename.isEmpty());
    
270 271
    _appendStatusLog("Downloading firmware...");
    _appendStatusLog(QString(" From: %1").arg(_firmwareFilename));
272 273 274 275 276 277 278 279 280 281
    
    // Split out filename from path
    QString firmwareFilename = QFileInfo(_firmwareFilename).fileName();
    Q_ASSERT(!firmwareFilename.isEmpty());
    
    // Determine location to download file to
    QString downloadFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
    if (downloadFile.isEmpty()) {
        downloadFile = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
        if (downloadFile.isEmpty()) {
282
            _errorCancel("Unabled to find writable download location. Tried downloads and temp directory.");
283 284 285 286
            return;
        }
    }
    Q_ASSERT(!downloadFile.isEmpty());
287
    downloadFile += "/"  + firmwareFilename;
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

    QUrl firmwareUrl;
    if (_firmwareFilename.startsWith("http:")) {
        firmwareUrl.setUrl(_firmwareFilename);
    } else {
        firmwareUrl = QUrl::fromLocalFile(_firmwareFilename);
    }
    Q_ASSERT(firmwareUrl.isValid());
    
    QNetworkRequest networkRequest(firmwareUrl);
    
    // Store download file location in user attribute so we can retrieve when the download finishes
    networkRequest.setAttribute(QNetworkRequest::User, downloadFile);
    
    _downloadManager = new QNetworkAccessManager(this);
    Q_CHECK_PTR(_downloadManager);
    _downloadNetworkReply = _downloadManager->get(networkRequest);
    Q_ASSERT(_downloadNetworkReply);
    connect(_downloadNetworkReply, &QNetworkReply::downloadProgress, this, &FirmwareUpgradeController::_downloadProgress);
    connect(_downloadNetworkReply, &QNetworkReply::finished, this, &FirmwareUpgradeController::_downloadFinished);
    // FIXME
    //connect(_downloadNetworkReply, &QNetworkReply::error, this, &FirmwareUpgradeController::_downloadError);
    connect(_downloadNetworkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(_downloadError(QNetworkReply::NetworkError)));
}

/// @brief Updates the progress indicator while downloading
void FirmwareUpgradeController::_downloadProgress(qint64 curr, qint64 total)
{
    // Take care of cases where 0 / 0 is emitted as error return value
    if (total > 0) {
318
        _progressBar->setProperty("value", (float)curr / (float)total);
319 320 321 322 323 324
    }
}

/// @brief Called when the firmware download completes.
void FirmwareUpgradeController::_downloadFinished(void)
{
325
    _appendStatusLog("Download complete");
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
    Q_ASSERT(reply);
    
    Q_ASSERT(_downloadNetworkReply == reply);
    
    _downloadManager->deleteLater();
    _downloadManager = NULL;
    
    // When an error occurs or the user cancels the download, we still end up here. So bail out in
    // those cases.
    if (reply->error() != QNetworkReply::NoError) {
        return;
    }
    
    // Download file location is in user attribute
    QString downloadFilename = reply->request().attribute(QNetworkRequest::User).toString();
    Q_ASSERT(!downloadFilename.isEmpty());
    
    // Store downloaded file in download location
    QFile file(downloadFilename);
    if (!file.open(QIODevice::WriteOnly)) {
348
        _errorCancel(QString("Could not save downloaded file to %1. Error: %2").arg(downloadFilename).arg(file.errorString()));
349 350 351 352 353
        return;
    }
    
    file.write(reply->readAll());
    file.close();
354
    FirmwareImage* image = new FirmwareImage(this);
355
    
356 357
    connect(image, &FirmwareImage::statusMessage, this, &FirmwareUpgradeController::_status);
    connect(image, &FirmwareImage::errorMessage, this, &FirmwareUpgradeController::_error);
358
    
359 360 361
    if (!image->load(downloadFilename, _bootloaderBoardID)) {
        _errorCancel("Image load failed");
        return;
362 363
    }
    
364 365 366
    // We can't proceed unless we have the bootloader
    if (!_bootloaderFound) {
        _errorCancel(QString("Bootloader not found").arg(_imageSize).arg(_bootloaderBoardFlashSize));
367 368
        return;
    }
369
    
370 371 372
    if (_bootloaderBoardFlashSize != 0 && _imageSize > _bootloaderBoardFlashSize) {
        _errorCancel(QString("Image size of %1 is too large for board flash size %2").arg(_imageSize).arg(_bootloaderBoardFlashSize));
        return;
373
    }
374 375

    _threadController->flash(image);
376 377
}

378 379 380
/// @brief Called when an error occurs during download
void FirmwareUpgradeController::_downloadError(QNetworkReply::NetworkError code)
{
381 382
    QString errorMsg;
    
383
    if (code == QNetworkReply::OperationCanceledError) {
384
        errorMsg = "Download cancelled";
385
    } else {
386
        errorMsg = QString("Error during download. Error: %1").arg(code);
387
    }
388
    _errorCancel(errorMsg);
389 390
}

391 392 393
/// @brief Signals completion of one of the specified bootloader commands. Moves the state machine to the
///         appropriate next step.
void FirmwareUpgradeController::_flashComplete(void)
394
{
395 396
    delete _image;
    _image = NULL;
397
    
398 399 400
    _appendStatusLog("Upgrade complete", true);
    _appendStatusLog("------------------------------------------", false);
    emit flashComplete();
401 402
}

403
void FirmwareUpgradeController::_error(const QString& errorString)
404
{
405 406 407 408
    delete _image;
    _image = NULL;
    
    _errorCancel(QString("Error: %1").arg(errorString));
409 410
}

411
void FirmwareUpgradeController::_status(const QString& statusString)
412
{
413
    _appendStatusLog(statusString);
414 415 416 417 418
}

/// @brief Updates the progress bar from long running bootloader commands
void FirmwareUpgradeController::_updateProgress(int curr, int total)
{
419 420 421 422
    // Take care of cases where 0 / 0 is emitted as error return value
    if (total > 0) {
        _progressBar->setProperty("value", (float)curr / (float)total);
    }
423 424 425 426 427 428
}

/// @brief Moves the progress bar ahead on tick while erasing the board
void FirmwareUpgradeController::_eraseProgressTick(void)
{
    _eraseTickCount++;
429
    _progressBar->setProperty("value", (float)(_eraseTickCount*_eraseTickMsec) / (float)_eraseTotalMsec);
430 431 432
}

/// Appends the specified text to the status log area in the ui
433
void FirmwareUpgradeController::_appendStatusLog(const QString& text, bool critical)
434 435 436 437
{
    Q_ASSERT(_statusLog);
    
    QVariant returnedValue;
438 439 440 441 442 443 444 445
    QVariant varText;
    
    if (critical) {
        varText = QString("<font color=\"yellow\">%1</font>").arg(text);
    } else {
        varText = text;
    }
    
446 447 448 449
    QMetaObject::invokeMethod(_statusLog,
                              "append",
                              Q_RETURN_ARG(QVariant, returnedValue),
                              Q_ARG(QVariant, varText));
Don Gagne's avatar
Don Gagne committed
450
}
451

452
bool FirmwareUpgradeController::qgcConnections(void)
453 454 455 456
{
    return LinkManager::instance()->anyConnectedLinks();
}

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
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)
480
{
481
    _eraseTimer.stop();
482
}