QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FirmwareUpgradeController.cc
Go to the documentation of this file.
3#include "Bootloader.h"
4#include "QGCFileDownload.h"
5#include "QGCOptions.h"
6#include "QGCCorePlugin.h"
9#include "SettingsManager.h"
10#include "JsonParsing.h"
11#include "LinkManager.h"
12#include "MultiVehicleManager.h"
13#include "FirmwareImage.h"
14#include "Fact.h"
15#include "QGCLoggingCategory.h"
16
17#include <QtCore/QDir>
18
19QGC_LOGGING_CATEGORY(FirmwareUpgradeControllerLog, "Vehicle.VehicleSetup.FirmwareUpgradeController")
20#include <QtCore/QStandardPaths>
21#include <QtCore/QJsonDocument>
22#include <QtCore/QJsonObject>
23#include <QtCore/QJsonArray>
24
31
32// See PX4 Bootloader board_types.txt - https://raw.githubusercontent.com/PX4/PX4-Bootloader/master/board_types.txt
33static QMap<int, QString> px4_board_name_map {
34 {9, "px4_fmu-v2_default"},
35 {255, "px4_fmu-v3_default"}, // Simulated board id for V3 which is a V2 board which supports larger flash space
36 {11, "px4_fmu-v4_default"},
37 {13, "px4_fmu-v4pro_default"},
38 {20, "uvify_core_default"},
39 {50, "px4_fmu-v5_default"},
40 {51, "px4_fmu-v5x_default"},
41 {52, "px4_fmu-v6_default"},
42 {53, "px4_fmu-v6x_default"},
43 {54, "px4_fmu-v6u_default"},
44 {56, "px4_fmu-v6c_default"},
45 {57, "ark_fmu-v6x_default"},
46 {59, "ark_fpv_default"},
47 {35, "px4_fmu-v6xrt_default"},
48 {55, "sky-drones_smartap-airlink_default"},
49 {88, "airmind_mindpx-v2_default"},
50 {12, "bitcraze_crazyflie_default"},
51 {14, "bitcraze_crazyflie21_default"},
52 {42, "omnibus_f4sd_default"},
53 {33, "mro_x21_default"},
54 {65, "intel_aerofc-v1_default"},
55 {123, "holybro_kakutef7_default"},
56 {41775, "modalai_fc-v1_default"},
57 {41776, "modalai_fc-v2_default"},
58 {78, "holybro_pix32v5_default"},
59 {79, "holybro_can-gps-v1_default"},
60 {28, "nxp_fmuk66-v3_default"},
61 {30, "nxp_fmuk66-e_default"},
62 {31, "nxp_fmurt1062-v1_default"},
63 {37, "nxp_mr-tropic_default"},
64 {85, "freefly_can-rtk-gps_default"},
65 {120, "cubepilot_cubeyellow_default"},
66 {136, "mro_x21-777_default"},
67 {139, "holybro_durandal-v1_default"},
68 {140, "cubepilot_cubeorange_default"},
69 {1063, "cubepilot_cubeorangeplus_default"},
70 {141, "mro_ctrl-zero-f7_default"},
71 {142, "mro_ctrl-zero-f7-oem_default"},
72 {212, "thepeach_k1_default"},
73 {213, "thepeach_r1_default"},
74 {1009, "cuav_nora_default"},
75 {1010, "cuav_x7pro_default"},
76 {1017, "mro_pixracerpro_default"},
77 {1022, "mro_ctrl-zero-classic_default"},
78 {1023, "mro_ctrl-zero-h7_default"},
79 {1024, "mro_ctrl-zero-h7-oem_default"},
80 {1048, "holybro_kakuteh7_default"},
81 {1053, "holybro_kakuteh7v2_default"},
82 {1054, "holybro_kakuteh7mini_default"},
83 {1058, "holybro_kakuteh7mini_default"},
84 {1105, "holybro_kakuteh7-wing_default"},
85 {1110, "jfb_jfb110_default"},
86 {1200, "jfb_jfb200_default"},
87 {1209, "gearup_airbrainh743_default"},
88 {1218, "amovlab_flycore_default"},
89 {1198, "aedrox_aedroxh7_default"},
90 {1123, "siyi_n7_default"},
91 {1124, "3dr_ctrl-zero-h7-oem-revg_default"},
92 {1949, "agam_fmu-v6xrt_default"},
93 {5600, "zeroone_x6_default"},
94 {6110, "svehicle_e2_default"},
95 {7000, "cuav_7-nano_default"},
96 {7001, "cuav_fmu-v6x_default"},
97 {7002, "cuav_x25-evo_default"},
98 {7003, "cuav_x25-super_default"},
99 {7004, "cuav_x25-mega_default"},
100 {7120, "accton-godwit_ga1_default"}
101};
102
104{
105 return static_cast<uint>(( firmwareId.autopilotStackType |
106 (firmwareId.firmwareType << 8) |
107 (firmwareId.firmwareVehicleType << 16) ));
108}
109
112 : _singleFirmwareURL (QGCCorePlugin::instance()->options()->firmwareUpgradeSingleURL())
113 , _singleFirmwareMode (!_singleFirmwareURL.isEmpty())
114 , _downloadingFirmwareList (false)
115 , _statusLog (nullptr)
116 , _selectedFirmwareBuildType (StableFirmware)
117 , _image (nullptr)
118 , _apmBoardDescriptionReplaceText ("<APMBoardDescription>")
119 , _apmChibiOSSetting (SettingsManager::instance()->firmwareUpgradeSettings()->apmChibiOS())
120 , _apmVehicleTypeSetting (SettingsManager::instance()->firmwareUpgradeSettings()->apmVehicleType())
121{
122 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["OFFICIAL"] = StableFirmware;
123 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["BETA"] = BetaFirmware;
124 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["DEV"] = DeveloperFirmware;
125
126 _manifestMavTypeToFirmwareVehicleTypeMap["Copter"] = CopterFirmware;
127 _manifestMavTypeToFirmwareVehicleTypeMap["HELICOPTER"] = HeliFirmware;
128 _manifestMavTypeToFirmwareVehicleTypeMap["FIXED_WING"] = PlaneFirmware;
129 _manifestMavTypeToFirmwareVehicleTypeMap["GROUND_ROVER"] = RoverFirmware;
130 _manifestMavTypeToFirmwareVehicleTypeMap["SUBMARINE"] = SubFirmware;
131
132 _threadController = new PX4FirmwareUpgradeThreadController(this);
133 Q_CHECK_PTR(_threadController);
134
135 connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &FirmwareUpgradeController::_foundBoard);
136 connect(_threadController, &PX4FirmwareUpgradeThreadController::noBoardFound, this, &FirmwareUpgradeController::_noBoardFound);
137 connect(_threadController, &PX4FirmwareUpgradeThreadController::boardGone, this, &FirmwareUpgradeController::_boardGone);
138 connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoardInfo, this, &FirmwareUpgradeController::_foundBoardInfo);
139 connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error);
140 connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
141 connect(_threadController, &PX4FirmwareUpgradeThreadController::status, this, &FirmwareUpgradeController::_status);
142 connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseStarted, this, &FirmwareUpgradeController::_eraseStarted);
143 connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseComplete, this, &FirmwareUpgradeController::_eraseComplete);
144 connect(_threadController, &PX4FirmwareUpgradeThreadController::flashComplete, this, &FirmwareUpgradeController::_flashComplete);
145 connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
146 connect(_threadController, &PX4FirmwareUpgradeThreadController::portsAvailable, this, &FirmwareUpgradeController::_portsAvailable);
147
148 connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
149
150 // Only offer ArduPilot firmware when an ArduPilot plugin factory is registered
152 connect(_apmChibiOSSetting, &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
153 connect(_apmVehicleTypeSetting, &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
154 }
155
156 _determinePX4StableVersion();
157
159 _downloadArduPilotManifest();
160 }
161}
162
167
169{
170 LinkManager::instance()->setConnectionsSuspended(tr("Connect not allowed during Firmware Upgrade."));
171
172 // FIXME: Why did we get here with active vehicle?
173 if (!MultiVehicleManager::instance()->activeVehicle()) {
174 // We have to disconnect any inactive links
176 }
177
178 _bootloaderFound = false;
179 _startFlashWhenBootloaderFound = false;
180 _threadController->startFindBoardLoop();
181}
182
184 FirmwareBuildType_t firmwareType,
185 FirmwareVehicleType_t vehicleType)
186{
187 qCDebug(FirmwareUpgradeControllerLog) << "FirmwareUpgradeController::flash stackType:firmwareType:vehicleType" << stackType << firmwareType << vehicleType;
188 FirmwareIdentifier firmwareId = FirmwareIdentifier(stackType, firmwareType, vehicleType);
189 if (_bootloaderFound) {
190 _getFirmwareFile(firmwareId);
191 } else {
192 // We haven't found the bootloader yet. Need to wait until then to flash
193 _startFlashWhenBootloaderFound = true;
194 _startFlashWhenBootloaderFoundFirmwareIdentity = firmwareId;
195 _firmwareFilename.clear();
196 }
197}
198
200{
201 _firmwareFilename = firmwareFlashUrl;
202 if (_bootloaderFound) {
203 _downloadFirmware();
204 } else {
205 // We haven't found the bootloader yet. Need to wait until then to flash
206 _startFlashWhenBootloaderFound = true;
207 }
208}
209
211{
212 flash(firmwareId.autopilotStackType, firmwareId.firmwareType, firmwareId.firmwareVehicleType);
213}
214
219
221{
222 _eraseTimer.stop();
223 _flashCancelled = true;
224 if (_activeDownloader) {
225 _activeDownloader->cancel();
226 }
227 _threadController->cancel();
228}
229
230void FirmwareUpgradeController::flashPort(const QString& systemLocation)
231{
232 qCDebug(FirmwareUpgradeControllerLog) << "flashPort" << systemLocation;
233 _bootloaderFound = false;
234 _startFlashWhenBootloaderFound = false;
235 _threadController->setTargetPort(systemLocation);
236}
237
238void FirmwareUpgradeController::_portsAvailable(const QVariantList& ports)
239{
240 _availablePorts = ports;
242}
243
245{
247 QString boardName;
248 QStringList names;
249
251 for (const auto& info : ports) {
252 if(info.canFlash()) {
253 info.getBoardInfo(boardType, boardName);
254 names.append(boardName);
255 }
256 }
257
258 return names;
259}
260
261void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int boardType, QString boardName)
262{
263 _boardInfo = info;
264 _boardType = static_cast<QGCSerialPortInfo::BoardType_t>(boardType);
265 if (!boardName.isEmpty()) {
266 _boardTypeName = boardName;
267 } else if (!info.description().isEmpty()) {
268 _boardTypeName = info.description();
269 } else {
270 _boardTypeName = info.portName();
271 }
272
273 qDebug() << info.manufacturer() << info.description();
274
275 _startFlashWhenBootloaderFound = false;
276
277 if (_boardType == QGCSerialPortInfo::BoardTypeSiKRadio) {
278 if (!firstAttempt) {
279 // Radio always flashes latest firmware, so we can start right away without
280 // any further user input.
281 _startFlashWhenBootloaderFound = true;
282 _startFlashWhenBootloaderFoundFirmwareIdentity = FirmwareIdentifier(SiKRadio,
285 }
286 }
287
288 qCDebug(FirmwareUpgradeControllerLog) << _boardType << _boardTypeName;
289 emit boardFound();
290}
291
292
293void FirmwareUpgradeController::_noBoardFound(void)
294{
295 emit noBoardFound();
296}
297
298void FirmwareUpgradeController::_boardGone(void)
299{
300 emit boardGone();
301}
302
305void FirmwareUpgradeController::_foundBoardInfo(int bootloaderVersion, int boardID, int flashSize)
306{
307 _bootloaderFound = true;
308 _bootloaderVersion = static_cast<uint32_t>(bootloaderVersion);
309 _bootloaderBoardID = static_cast<uint32_t>(boardID);
310 _bootloaderBoardFlashSize = static_cast<uint32_t>(flashSize);
311
312 _appendStatusLog(tr("Connected to bootloader:"));
313 _appendStatusLog(tr(" Version: %1").arg(_bootloaderVersion));
314 _appendStatusLog(tr(" Board ID: %1").arg(_bootloaderBoardID));
315 _appendStatusLog(tr(" Flash size: %1").arg(_bootloaderBoardFlashSize));
316
317 if (_startFlashWhenBootloaderFound) {
318 flash(_startFlashWhenBootloaderFoundFirmwareIdentity);
319 } else {
320 if (_rgManifestFirmwareInfo.length()) {
321 _buildAPMFirmwareNames();
322 }
324 }
325}
326
329void FirmwareUpgradeController::_bootloaderSyncFailed(void)
330{
331 _errorCancel(tr("Unable to sync with bootloader."));
332}
333
334QHash<FirmwareUpgradeController::FirmwareIdentifier, QString>* FirmwareUpgradeController::_firmwareHashForBoardId(int boardId)
335{
336 _rgFirmwareDynamic.clear();
337
338 switch (boardId) {
340 {
341 FirmwareToUrlElement_t element = { SiKRadio, StableFirmware, DefaultVehicleFirmware, "https://firmware.ardupilot.org/SiK/stable/radio~hm_trp.ihx" };
342 _rgFirmwareDynamic.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
343 }
344 break;
346 {
347 FirmwareToUrlElement_t element = { SiKRadio, StableFirmware, DefaultVehicleFirmware, "https://firmware.ardupilot.org/SiK/stable/radio~hb1060.ihx" };
348 _rgFirmwareDynamic.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
349 }
350 break;
351 default:
352 if (px4_board_name_map.contains(boardId)) {
353 const QString px4Url{"http://px4-travis.s3.amazonaws.com/Firmware/%1/%2.px4"};
354
355 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, StableFirmware, DefaultVehicleFirmware), px4Url.arg("stable").arg(px4_board_name_map.value(boardId)));
356 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, BetaFirmware, DefaultVehicleFirmware), px4Url.arg("beta").arg(px4_board_name_map.value(boardId)));
357 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware), px4Url.arg("master").arg(px4_board_name_map.value(boardId)));
358 }
359 break;
360 }
361
362 return &_rgFirmwareDynamic;
363}
364
365void FirmwareUpgradeController::_getFirmwareFile(FirmwareIdentifier firmwareId)
366{
367 QHash<FirmwareIdentifier, QString>* prgFirmware = _firmwareHashForBoardId(static_cast<int>(_bootloaderBoardID));
368 if (firmwareId.firmwareType == CustomFirmware) {
369 _firmwareFilename = QString();
370 _errorCancel(tr("Custom firmware selected but no filename given."));
371 } else {
372 if (prgFirmware->contains(firmwareId)) {
373 _firmwareFilename = prgFirmware->value(firmwareId);
374 } else {
375 _errorCancel(tr("Unable to find specified firmware for board type"));
376 return;
377 }
378 }
379
380 if (_firmwareFilename.isEmpty()) {
381 _errorCancel(tr("No firmware file selected"));
382 } else {
383 _downloadFirmware();
384 }
385}
386
388void FirmwareUpgradeController::_downloadFirmware(void)
389{
390 Q_ASSERT(!_firmwareFilename.isEmpty());
391
392 _flashCancelled = false;
393
394 const bool isRemote = _firmwareFilename.startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
395 if (isRemote) {
396 _appendStatusLog(tr("Downloading firmware from %1").arg(_firmwareFilename));
397 } else {
398 _appendStatusLog(tr("Using firmware file %1").arg(_firmwareFilename));
399 }
400
401 QGCFileDownload* downloader = new QGCFileDownload(this);
402 _activeDownloader = downloader;
403 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
404 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_firmwareDownloadComplete);
405 connect(downloader, &QGCFileDownload::downloadProgress, this, &FirmwareUpgradeController::_firmwareDownloadProgress);
406 if (!downloader->start(_firmwareFilename)) {
407 downloader->deleteLater();
408 _activeDownloader = nullptr;
409 _errorCancel(downloader->errorString());
410 return;
411 }
412}
413
415void FirmwareUpgradeController::_firmwareDownloadProgress(qint64 curr, qint64 total)
416{
417 // Take care of cases where 0 / 0 is emitted as error return value
418 if (total > 0) {
419 _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
420 }
421}
422
424void FirmwareUpgradeController::_firmwareDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
425{
426 _activeDownloader = nullptr;
427
428 if (_flashCancelled) {
429 // User cancelled while the download was in flight; ignore late completion.
430 return;
431 }
432
433 if (success) {
434 const bool isRemote = _firmwareFilename.startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
435 if (isRemote) {
436 _appendStatusLog(tr("Download complete"));
437 }
438
439 FirmwareImage* image = new FirmwareImage(this);
440
441 connect(image, &FirmwareImage::statusMessage, this, &FirmwareUpgradeController::_status);
442 connect(image, &FirmwareImage::errorMessage, this, &FirmwareUpgradeController::_error);
443
444 if (!image->load(localFile, _bootloaderBoardID)) {
445 _errorCancel(tr("Image load failed"));
446 return;
447 }
448
449 // We can't proceed unless we have the bootloader
450 if (!_bootloaderFound) {
451 _errorCancel(tr("Bootloader not found"));
452 return;
453 }
454
455 if (_bootloaderBoardFlashSize != 0 && image->imageSize() > _bootloaderBoardFlashSize) {
456 _errorCancel(tr("Image size of %1 is too large for board flash size %2").arg(image->imageSize()).arg(_bootloaderBoardFlashSize));
457 return;
458 }
459
460 _threadController->flash(image);
461 } else if (!errorMsg.isEmpty()) {
462 _errorCancel(errorMsg);
463 }
464}
465
468{
469 switch (type) {
470 case StableFirmware:
471 return "stable";
473 return "developer";
474 case BetaFirmware:
475 return "beta";
476 default:
477 return "custom";
478 }
479}
480
483void FirmwareUpgradeController::_flashComplete(void)
484{
485 delete _image;
486 _image = nullptr;
487
488 _appendStatusLog(tr("Upgrade complete"), true);
489 emit flashComplete();
491}
492
493void FirmwareUpgradeController::_error(const QString& errorString)
494{
495 delete _image;
496 _image = nullptr;
497
498 _errorCancel(tr("Error: %1").arg(errorString));
499}
500
501void FirmwareUpgradeController::_status(const QString& statusString)
502{
503 _appendStatusLog(statusString);
504}
505
507void FirmwareUpgradeController::_updateProgress(int curr, int total)
508{
509 // Take care of cases where 0 / 0 is emitted as error return value
510 if (total > 0) {
511 _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
512 }
513}
514
516void FirmwareUpgradeController::_eraseProgressTick(void)
517{
518 _eraseTickCount++;
519 _progressBar->setProperty("value", static_cast<float>(_eraseTickCount*_eraseTickMsec) / static_cast<float>(_eraseTotalMsec));
520}
521
523void FirmwareUpgradeController::_appendStatusLog(const QString& text, bool critical)
524{
525 Q_ASSERT(_statusLog);
526
527 QString varText;
528
529 if (critical) {
530 varText = QString("<b>%1</b>").arg(text);
531 } else {
532 varText = text;
533 }
534
535 QMetaObject::invokeMethod(_statusLog,
536 "append",
537 Q_ARG(QString, varText));
538}
539
540void FirmwareUpgradeController::_errorCancel(const QString& msg)
541{
542 _appendStatusLog(msg, false);
543 _appendStatusLog(tr("Upgrade cancelled"), true);
544 emit error();
545 cancel();
547}
548
549void FirmwareUpgradeController::_eraseStarted(void)
550{
551 // We set up our own progress bar for erase since the erase command does not provide one
552 _eraseTickCount = 0;
553 _eraseTimer.start(_eraseTickMsec);
554 emit eraseStarted();
555}
556
557void FirmwareUpgradeController::_eraseComplete(void)
558{
559 _eraseTimer.stop();
560}
561
563{
564 _selectedFirmwareBuildType = firmwareType;
565 emit selectedFirmwareBuildTypeChanged(_selectedFirmwareBuildType);
566 _buildAPMFirmwareNames();
567}
568
569void FirmwareUpgradeController::_buildAPMFirmwareNames(void)
570{
572 return;
573 }
574
575 bool chibios = _apmChibiOSSetting->rawValue().toInt() == 0;
576 FirmwareVehicleType_t vehicleType = static_cast<FirmwareVehicleType_t>(_apmVehicleTypeSetting->rawValue().toInt());
577 QString boardDescription = _boardInfo.description();
578 quint16 boardVID = _boardInfo.vendorIdentifier();
579 quint16 boardPID = _boardInfo.productIdentifier();
580 uint32_t rawBoardId = _bootloaderBoardID == Bootloader::boardIDPX4FMUV3 ? Bootloader::boardIDPX4FMUV2 : _bootloaderBoardID;
581
582 qCDebug(FirmwareUpgradeControllerLog) << QStringLiteral("_buildAPMFirmwareNames description(%1) vid(%2/0x%3) pid(%4/0x%5)").arg(boardDescription).arg(boardVID).arg(boardVID, 1, 16).arg(boardPID).arg(boardPID, 1, 16);
583
584 _apmFirmwareNames.clear();
585 _apmFirmwareNamesBestIndex = -1;
586 _apmFirmwareUrls.clear();
587
588 QString apmDescriptionSuffix("-BL");
589 QString boardDescriptionPrefix;
590 bool bootloaderMatch = boardDescription.endsWith(apmDescriptionSuffix);
591
592 int currentIndex = 0;
593 for (const ManifestFirmwareInfo_t& firmwareInfo: _rgManifestFirmwareInfo) {
594 bool match = false;
595 if (firmwareInfo.firmwareBuildType == _selectedFirmwareBuildType && firmwareInfo.chibios == chibios && firmwareInfo.vehicleType == vehicleType && firmwareInfo.boardId == rawBoardId) {
596 if (firmwareInfo.fmuv2 && _bootloaderBoardID == Bootloader::boardIDPX4FMUV3) {
597 qCDebug(FirmwareUpgradeControllerLog) << "Skipping fmuv2 manifest entry for fmuv3 board:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
598 } else {
599 qCDebug(FirmwareUpgradeControllerLog) << "Board id match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
600 match = true;
601 if (bootloaderMatch && _apmFirmwareNamesBestIndex == -1 && firmwareInfo.rgBootloaderPortString.contains(boardDescription)) {
602 _apmFirmwareNamesBestIndex = currentIndex;
603 qCDebug(FirmwareUpgradeControllerLog) << "Bootloader best match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
604 }
605 }
606 }
607
608 if (match) {
609 _apmFirmwareNames.append(firmwareInfo.friendlyName);
610 _apmFirmwareUrls.append(firmwareInfo.url);
611 currentIndex++;
612 }
613 }
614
615 if (_apmFirmwareNamesBestIndex == -1) {
616 _apmFirmwareNamesBestIndex++;
617 if (_apmFirmwareNames.length() > 1) {
618 _apmFirmwareNames.prepend(tr("Choose board type"));
619 _apmFirmwareUrls.prepend(QString());
620 }
621 }
622
624}
625
627{
628 if (index < 0 || index >= _apmVehicleTypeFromCurrentVersionList.length()) {
629 qWarning() << "Invalid index, index:count" << index << _apmVehicleTypeFromCurrentVersionList.length();
630 return CopterFirmware;
631 }
632
633 return _apmVehicleTypeFromCurrentVersionList[index];
634}
635
636void FirmwareUpgradeController::_determinePX4StableVersion(void)
637{
638 QGCFileDownload* downloader = new QGCFileDownload(this);
639 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
640 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete);
641 if (!downloader->start(QStringLiteral("https://api.github.com/repos/PX4/Firmware/releases"))) {
642 qCWarning(FirmwareUpgradeControllerLog) << "PX4 releases github download did not start:" << downloader->errorString();
643 downloader->deleteLater();
644 return;
645 }
646}
647
648void FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
649{
650 if (success) {
651 QFile jsonFile(localFile);
652 if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
653 qCWarning(FirmwareUpgradeControllerLog) << "Unable to open github px4 releases json file" << localFile << jsonFile.errorString();
654 return;
655 }
656 QByteArray bytes = jsonFile.readAll();
657 jsonFile.close();
658
659 QJsonParseError jsonParseError;
660 QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
661 if (jsonParseError.error != QJsonParseError::NoError) {
662 qCWarning(FirmwareUpgradeControllerLog) << "Unable to open px4 releases json document" << localFile << jsonParseError.errorString();
663 return;
664 }
665
666 // Json should be an array of release objects
667 if (!doc.isArray()) {
668 qCWarning(FirmwareUpgradeControllerLog) << "px4 releases json document is not an array" << localFile;
669 return;
670 }
671 QJsonArray releases = doc.array();
672
673 // The first release marked prerelease=false is stable
674 // The first release marked prerelease=true is beta
675 bool foundStable = false;
676 bool foundBeta = false;
677 for (int i=0; i<releases.count() && (!foundStable || !foundBeta); i++) {
678 QJsonObject release = releases[i].toObject();
679 if (!foundStable && !release["prerelease"].toBool()) {
680 _px4StableVersion = release["name"].toString();
681 emit px4StableVersionChanged(_px4StableVersion);
682 qCDebug(FirmwareUpgradeControllerLog()) << "Found px4 stable version" << _px4StableVersion;
683 foundStable = true;
684 } else if (!foundBeta && release["prerelease"].toBool()) {
685 _px4BetaVersion = release["name"].toString();
686 emit px4BetaVersionChanged(_px4BetaVersion);
687 qCDebug(FirmwareUpgradeControllerLog()) << "Found px4 beta version" << _px4BetaVersion;
688 foundBeta = true;
689 }
690 }
691
692 if (!foundStable) {
693 qCDebug(FirmwareUpgradeControllerLog()) << "Unable to find px4 stable version" << localFile;
694 }
695 if (!foundBeta) {
696 qCDebug(FirmwareUpgradeControllerLog()) << "Unable to find px4 beta version" << localFile;
697 }
698 } else if (!errorMsg.isEmpty()) {
699 qCWarning(FirmwareUpgradeControllerLog) << "PX4 releases github download failed" << errorMsg;
700 }
701}
702
703void FirmwareUpgradeController::_downloadArduPilotManifest(void)
704{
705 _downloadingFirmwareList = true;
707
708 QGCFileDownload* downloader = new QGCFileDownload(this);
709 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
710 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_ardupilotManifestDownloadComplete);
711 // Use autoDecompress to stream-decompress directly during download
712 downloader->setAutoDecompress(true);
713 if (!downloader->start(QStringLiteral("https://firmware.ardupilot.org/manifest.json.gz"))) {
714 qCWarning(FirmwareUpgradeControllerLog) << "ArduPilot Manifest download did not start:" << downloader->errorString();
715 downloader->deleteLater();
716 _downloadingFirmwareList = false;
718 }
719}
720
721void FirmwareUpgradeController::_ardupilotManifestDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
722{
723 const auto clearDownloadState = [this]() {
724 if (_downloadingFirmwareList) {
725 _downloadingFirmwareList = false;
727 }
728 };
729
730 if (success) {
731 qCDebug(FirmwareUpgradeControllerLog) << "_ardupilotManifestDownloadFinished" << localFile;
732
733 // localFile is already decompressed (autoDecompress=true streams directly to .json)
734 QString errorString;
735 QJsonDocument doc;
736 if (!JsonParsing::isJsonFile(localFile, doc, errorString)) {
737 qCWarning(FirmwareUpgradeControllerLog) << "Json file read failed" << errorString;
738 clearDownloadState();
739 return;
740 }
741
742 QJsonObject json = doc.object();
743 QJsonArray rgFirmware = json[_manifestFirmwareJsonKey].toArray();
744
745 for (int i=0; i<rgFirmware.count(); i++) {
746 const QJsonObject& firmwareJson = rgFirmware[i].toObject();
747
748 FirmwareVehicleType_t firmwareVehicleType = _manifestMavTypeToFirmwareVehicleType(firmwareJson[_manifestMavTypeJsonKey].toString());
749 FirmwareBuildType_t firmwareBuildType = _manifestMavFirmwareVersionTypeToFirmwareBuildType(firmwareJson[_manifestMavFirmwareVersionTypeJsonKey].toString());
750 QString format = firmwareJson[_manifestFormatJsonKey].toString();
751 QString platform = firmwareJson[_manifestPlatformKey].toString();
752
753 if (firmwareVehicleType != DefaultVehicleFirmware && firmwareBuildType != CustomFirmware && (format == QStringLiteral("apj") || format == QStringLiteral("px4"))) {
754 if (platform.contains("-heli") && firmwareVehicleType != HeliFirmware) {
755 continue;
756 }
757
758 _rgManifestFirmwareInfo.append(ManifestFirmwareInfo_t());
759 ManifestFirmwareInfo_t& firmwareInfo = _rgManifestFirmwareInfo.last();
760
761 firmwareInfo.boardId = static_cast<uint32_t>(firmwareJson[_manifestBoardIdJsonKey].toInt());
762 firmwareInfo.firmwareBuildType = firmwareBuildType;
763 firmwareInfo.vehicleType = firmwareVehicleType;
764 firmwareInfo.url = firmwareJson[_manifestUrlJsonKey].toString();
765 firmwareInfo.version = firmwareJson[_manifestMavFirmwareVersionJsonKey].toString();
766 firmwareInfo.chibios = format == QStringLiteral("apj"); firmwareInfo.fmuv2 = platform.contains(QStringLiteral("fmuv2"));
767
768 QJsonArray bootloaderArray = firmwareJson[_manifestBootloaderStrJsonKey].toArray();
769 for (int j=0; j<bootloaderArray.count(); j++) {
770 firmwareInfo.rgBootloaderPortString.append(bootloaderArray[j].toString());
771 }
772
773 QJsonArray usbidArray = firmwareJson[_manifestUSBIDJsonKey].toArray();
774 for (int j=0; j<usbidArray.count(); j++) {
775 QStringList vidpid = usbidArray[j].toString().split('/');
776 QString vid = vidpid[0];
777 QString pid = vidpid[1];
778
779 bool ok;
780 firmwareInfo.rgVID.append(vid.right(vid.length() - 2).toInt(&ok, 16));
781 firmwareInfo.rgPID.append(pid.right(pid.length() - 2).toInt(&ok, 16));
782 }
783
784 QString brandName = firmwareJson[_manifestBrandNameKey].toString();
785 firmwareInfo.friendlyName = QStringLiteral("%1 - %2").arg(brandName.isEmpty() ? platform : brandName).arg(firmwareInfo.version);
786 }
787 }
788
789 if (_bootloaderFound) {
790 _buildAPMFirmwareNames();
791 }
792
793 clearDownloadState();
794 } else if (!errorMsg.isEmpty()) {
795 qCWarning(FirmwareUpgradeControllerLog) << "ArduPilot Manifest download failed" << errorMsg;
796 clearDownloadState();
797 }
798}
799
800FirmwareUpgradeController::FirmwareBuildType_t FirmwareUpgradeController::_manifestMavFirmwareVersionTypeToFirmwareBuildType(const QString& manifestMavFirmwareVersionType)
801{
802 if (_manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap.contains(manifestMavFirmwareVersionType)) {
803 return _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap[manifestMavFirmwareVersionType];
804 } else {
805 return CustomFirmware;
806 }
807}
808
809FirmwareUpgradeController::FirmwareVehicleType_t FirmwareUpgradeController::_manifestMavTypeToFirmwareVehicleType(const QString& manifestMavType)
810{
811 if (_manifestMavTypeToFirmwareVehicleTypeMap.contains(manifestMavType)) {
812 return _manifestMavTypeToFirmwareVehicleTypeMap[manifestMavType];
813 } else {
815 }
816}
uint qHash(const FirmwareUpgradeController::FirmwareIdentifier &firmwareId)
static QMap< int, QString > px4_board_name_map
QString errorString
Unified file download utility with decompression, verification, and QML support.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static const int boardIDSiKRadio1060
Newer radio based on SI1060 chip.
Definition Bootloader.h:34
static const int boardIDSiKRadio1000
Original radio based on SI1000 chip.
Definition Bootloader.h:33
static const int boardIDPX4FMUV2
PX4 V2 board, as from USB PID.
Definition Bootloader.h:38
static const int boardIDPX4FMUV3
Definition Bootloader.h:39
void rawValueChanged(const QVariant &value)
QVariant rawValue() const
Definition Fact.h:87
Support for Intel Hex firmware file.
bool load(const QString &imageFilename, uint32_t boardId)
void statusMessage(const QString &warningtring)
uint32_t imageSize(void) const
Returns the number of bytes in the image.
void errorMessage(const QString &errorString)
static FirmwarePluginManager * instance()
Q_INVOKABLE void startBoardSearch(void)
TextArea for log output.
Q_INVOKABLE void flash(AutoPilotStackType_t stackType, FirmwareBuildType_t firmwareType=StableFirmware, FirmwareVehicleType_t vehicleType=DefaultVehicleFirmware)
Called when the firmware type has been selected by the user to continue the flash process.
void apmFirmwareNamesChanged(void)
Q_INVOKABLE FirmwareVehicleType_t vehicleTypeFromFirmwareSelectionIndex(int index)
void px4BetaVersionChanged(const QString &px4BetaVersion)
void px4StableVersionChanged(const QString &px4StableVersion)
FirmwareUpgradeController(void)
@Brief Constructs a new FirmwareUpgradeController Widget. This widget is used within the PX4VehicleCo...
Q_INVOKABLE void cancel(void)
Cancels whatever state the upgrade worker thread is in.
void showFirmwareSelectDlg(void)
Q_INVOKABLE void flashSingleFirmwareMode(FirmwareBuildType_t firmwareType)
Called to flash when upgrade is running in singleFirmwareMode.
Q_INVOKABLE void flashPort(const QString &systemLocation)
Selects which serial port to flash. The worker will wait for this port to be in bootloader mode and t...
Q_INVOKABLE QStringList availableBoardsName(void)
Return a human friendly string of available boards.
void selectedFirmwareBuildTypeChanged(FirmwareBuildType_t firmwareType)
QString firmwareTypeAsString(FirmwareBuildType_t type) const
returns firmware type as a string
void setSelectedFirmwareBuildType(FirmwareBuildType_t firmwareType)
void downloadingFirmwareListChanged(bool downloadingFirmwareList)
Q_INVOKABLE void flashFirmwareUrl(QString firmwareUrl)
void availablePortsChanged(void)
void setConnectionsAllowed()
Sets the flag to allow new connections to be made.
Definition LinkManager.h:80
static LinkManager * instance()
void setConnectionsSuspended(const QString &reason)
Definition LinkManager.h:77
void disconnectAll()
static MultiVehicleManager * instance()
Provides methods to interact with the bootloader. The commands themselves are signalled across to PX4...
void updateProgress(int curr, int total)
void portsAvailable(const QVariantList &ports)
void setTargetPort(const QString &systemLocation)
Tells the worker to flash a specific port (selected by the user) when it appears in bootloader mode.
void error(const QString &errorString)
void startFindBoardLoop(void)
Begins the process of searching for a supported board connected to any serial port....
void flash(const FirmwareImage *image)
void foundBoard(bool firstAttempt, const QGCSerialPortInfo &portInfo, int boardType, QString boardName)
void status(const QString &status)
void foundBoardInfo(int bootloaderVersion, int boardID, int flashSize)
Extension mechanism for generic, non-firmware-specific customization of QGC.
File download with progress, decompression, and hash verification.
void finished(bool success, const QString &localPath, const QString &errorMessage)
bool start(const QString &remoteUrl)
QString errorString() const
void setAutoDecompress(bool enabled)
void downloadProgress(qint64 bytesReceived, qint64 totalBytes)
Emitted during download with byte counts.
static QList< QGCSerialPortInfo > availablePorts()
Override of QSerialPortInfo::availablePorts.
Provides information about existing serial ports.
quint16 productIdentifier() const
Returns the 16-bit product number for the serial port, if available; otherwise returns zero.
QString manufacturer() const
Returns the manufacturer string of the serial port, if available; otherwise returns an empty string.
QString portName() const
Returns the name of the serial port.
QString description() const
Returns the description string of the serial port, if available; otherwise returns an empty string.
quint16 vendorIdentifier() const
Returns the 16-bit vendor number for the serial port, if available; otherwise returns zero.
Provides access to all app settings.
bool isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, QString &errorString)
Determines whether an in-memory byte buffer contains parseable JSON content.
QByteArray format(const QList< LogEntry > &entries, int fmt)
FirmwareUpgradeController::FirmwareVehicleType_t vehicleType
FirmwareUpgradeController::FirmwareBuildType_t firmwareType
FirmwareUpgradeController::AutoPilotStackType_t stackType