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 {1198, "aedrox_aedroxh7_default"},
89 {1123, "siyi_n7_default"},
90 {1124, "3dr_ctrl-zero-h7-oem-revg_default"},
91 {5600, "zeroone_x6_default"},
92 {6110, "svehicle_e2_default"},
93 {7000, "cuav_7-nano_default"},
94 {7001, "cuav_fmu-v6x_default"},
95 {7002, "cuav_x25-evo_default"},
96 {7003, "cuav_x25-super_default"},
97 {7004, "cuav_x25-mega_default"},
98 {7120, "accton-godwit_ga1_default"}
99};
100
102{
103 return static_cast<uint>(( firmwareId.autopilotStackType |
104 (firmwareId.firmwareType << 8) |
105 (firmwareId.firmwareVehicleType << 16) ));
106}
107
110 : _singleFirmwareURL (QGCCorePlugin::instance()->options()->firmwareUpgradeSingleURL())
111 , _singleFirmwareMode (!_singleFirmwareURL.isEmpty())
112 , _downloadingFirmwareList (false)
113 , _statusLog (nullptr)
114 , _selectedFirmwareBuildType (StableFirmware)
115 , _image (nullptr)
116 , _apmBoardDescriptionReplaceText ("<APMBoardDescription>")
117 , _apmChibiOSSetting (SettingsManager::instance()->firmwareUpgradeSettings()->apmChibiOS())
118 , _apmVehicleTypeSetting (SettingsManager::instance()->firmwareUpgradeSettings()->apmVehicleType())
119{
120 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["OFFICIAL"] = StableFirmware;
121 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["BETA"] = BetaFirmware;
122 _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap["DEV"] = DeveloperFirmware;
123
124 _manifestMavTypeToFirmwareVehicleTypeMap["Copter"] = CopterFirmware;
125 _manifestMavTypeToFirmwareVehicleTypeMap["HELICOPTER"] = HeliFirmware;
126 _manifestMavTypeToFirmwareVehicleTypeMap["FIXED_WING"] = PlaneFirmware;
127 _manifestMavTypeToFirmwareVehicleTypeMap["GROUND_ROVER"] = RoverFirmware;
128 _manifestMavTypeToFirmwareVehicleTypeMap["SUBMARINE"] = SubFirmware;
129
130 _threadController = new PX4FirmwareUpgradeThreadController(this);
131 Q_CHECK_PTR(_threadController);
132
133 connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoard, this, &FirmwareUpgradeController::_foundBoard);
134 connect(_threadController, &PX4FirmwareUpgradeThreadController::noBoardFound, this, &FirmwareUpgradeController::_noBoardFound);
135 connect(_threadController, &PX4FirmwareUpgradeThreadController::boardGone, this, &FirmwareUpgradeController::_boardGone);
136 connect(_threadController, &PX4FirmwareUpgradeThreadController::foundBoardInfo, this, &FirmwareUpgradeController::_foundBoardInfo);
137 connect(_threadController, &PX4FirmwareUpgradeThreadController::error, this, &FirmwareUpgradeController::_error);
138 connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
139 connect(_threadController, &PX4FirmwareUpgradeThreadController::status, this, &FirmwareUpgradeController::_status);
140 connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseStarted, this, &FirmwareUpgradeController::_eraseStarted);
141 connect(_threadController, &PX4FirmwareUpgradeThreadController::eraseComplete, this, &FirmwareUpgradeController::_eraseComplete);
142 connect(_threadController, &PX4FirmwareUpgradeThreadController::flashComplete, this, &FirmwareUpgradeController::_flashComplete);
143 connect(_threadController, &PX4FirmwareUpgradeThreadController::updateProgress, this, &FirmwareUpgradeController::_updateProgress);
144 connect(_threadController, &PX4FirmwareUpgradeThreadController::portsAvailable, this, &FirmwareUpgradeController::_portsAvailable);
145
146 connect(&_eraseTimer, &QTimer::timeout, this, &FirmwareUpgradeController::_eraseProgressTick);
147
148 // Only offer ArduPilot firmware when an ArduPilot plugin factory is registered
150 connect(_apmChibiOSSetting, &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
151 connect(_apmVehicleTypeSetting, &Fact::rawValueChanged, this, &FirmwareUpgradeController::_buildAPMFirmwareNames);
152 }
153
154 _determinePX4StableVersion();
155
157 _downloadArduPilotManifest();
158 }
159}
160
165
167{
168 LinkManager::instance()->setConnectionsSuspended(tr("Connect not allowed during Firmware Upgrade."));
169
170 // FIXME: Why did we get here with active vehicle?
171 if (!MultiVehicleManager::instance()->activeVehicle()) {
172 // We have to disconnect any inactive links
174 }
175
176 _bootloaderFound = false;
177 _startFlashWhenBootloaderFound = false;
178 _threadController->startFindBoardLoop();
179}
180
182 FirmwareBuildType_t firmwareType,
183 FirmwareVehicleType_t vehicleType)
184{
185 qCDebug(FirmwareUpgradeControllerLog) << "FirmwareUpgradeController::flash stackType:firmwareType:vehicleType" << stackType << firmwareType << vehicleType;
186 FirmwareIdentifier firmwareId = FirmwareIdentifier(stackType, firmwareType, vehicleType);
187 if (_bootloaderFound) {
188 _getFirmwareFile(firmwareId);
189 } else {
190 // We haven't found the bootloader yet. Need to wait until then to flash
191 _startFlashWhenBootloaderFound = true;
192 _startFlashWhenBootloaderFoundFirmwareIdentity = firmwareId;
193 _firmwareFilename.clear();
194 }
195}
196
198{
199 _firmwareFilename = firmwareFlashUrl;
200 if (_bootloaderFound) {
201 _downloadFirmware();
202 } else {
203 // We haven't found the bootloader yet. Need to wait until then to flash
204 _startFlashWhenBootloaderFound = true;
205 }
206}
207
209{
210 flash(firmwareId.autopilotStackType, firmwareId.firmwareType, firmwareId.firmwareVehicleType);
211}
212
217
219{
220 _eraseTimer.stop();
221 _flashCancelled = true;
222 if (_activeDownloader) {
223 _activeDownloader->cancel();
224 }
225 _threadController->cancel();
226}
227
228void FirmwareUpgradeController::flashPort(const QString& systemLocation)
229{
230 qCDebug(FirmwareUpgradeControllerLog) << "flashPort" << systemLocation;
231 _bootloaderFound = false;
232 _startFlashWhenBootloaderFound = false;
233 _threadController->setTargetPort(systemLocation);
234}
235
236void FirmwareUpgradeController::_portsAvailable(const QVariantList& ports)
237{
238 _availablePorts = ports;
240}
241
243{
245 QString boardName;
246 QStringList names;
247
249 for (const auto& info : ports) {
250 if(info.canFlash()) {
251 info.getBoardInfo(boardType, boardName);
252 names.append(boardName);
253 }
254 }
255
256 return names;
257}
258
259void FirmwareUpgradeController::_foundBoard(bool firstAttempt, const QSerialPortInfo& info, int boardType, QString boardName)
260{
261 _boardInfo = info;
262 _boardType = static_cast<QGCSerialPortInfo::BoardType_t>(boardType);
263 if (!boardName.isEmpty()) {
264 _boardTypeName = boardName;
265 } else if (!info.description().isEmpty()) {
266 _boardTypeName = info.description();
267 } else {
268 _boardTypeName = info.portName();
269 }
270
271 qDebug() << info.manufacturer() << info.description();
272
273 _startFlashWhenBootloaderFound = false;
274
275 if (_boardType == QGCSerialPortInfo::BoardTypeSiKRadio) {
276 if (!firstAttempt) {
277 // Radio always flashes latest firmware, so we can start right away without
278 // any further user input.
279 _startFlashWhenBootloaderFound = true;
280 _startFlashWhenBootloaderFoundFirmwareIdentity = FirmwareIdentifier(SiKRadio,
283 }
284 }
285
286 qCDebug(FirmwareUpgradeControllerLog) << _boardType << _boardTypeName;
287 emit boardFound();
288}
289
290
291void FirmwareUpgradeController::_noBoardFound(void)
292{
293 emit noBoardFound();
294}
295
296void FirmwareUpgradeController::_boardGone(void)
297{
298 emit boardGone();
299}
300
303void FirmwareUpgradeController::_foundBoardInfo(int bootloaderVersion, int boardID, int flashSize)
304{
305 _bootloaderFound = true;
306 _bootloaderVersion = static_cast<uint32_t>(bootloaderVersion);
307 _bootloaderBoardID = static_cast<uint32_t>(boardID);
308 _bootloaderBoardFlashSize = static_cast<uint32_t>(flashSize);
309
310 _appendStatusLog(tr("Connected to bootloader:"));
311 _appendStatusLog(tr(" Version: %1").arg(_bootloaderVersion));
312 _appendStatusLog(tr(" Board ID: %1").arg(_bootloaderBoardID));
313 _appendStatusLog(tr(" Flash size: %1").arg(_bootloaderBoardFlashSize));
314
315 if (_startFlashWhenBootloaderFound) {
316 flash(_startFlashWhenBootloaderFoundFirmwareIdentity);
317 } else {
318 if (_rgManifestFirmwareInfo.length()) {
319 _buildAPMFirmwareNames();
320 }
322 }
323}
324
327void FirmwareUpgradeController::_bootloaderSyncFailed(void)
328{
329 _errorCancel("Unable to sync with bootloader.");
330}
331
332QHash<FirmwareUpgradeController::FirmwareIdentifier, QString>* FirmwareUpgradeController::_firmwareHashForBoardId(int boardId)
333{
334 _rgFirmwareDynamic.clear();
335
336 switch (boardId) {
338 {
339 FirmwareToUrlElement_t element = { SiKRadio, StableFirmware, DefaultVehicleFirmware, "http://px4-travis.s3.amazonaws.com/SiK/stable/radio~hm_trp.ihx" };
340 _rgFirmwareDynamic.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
341 }
342 break;
344 {
345 FirmwareToUrlElement_t element = { SiKRadio, StableFirmware, DefaultVehicleFirmware, "https://px4-travis.s3.amazonaws.com/SiK/stable/radio~hb1060.ihx" };
346 _rgFirmwareDynamic.insert(FirmwareIdentifier(element.stackType, element.firmwareType, element.vehicleType), element.url);
347 }
348 break;
349 default:
350 if (px4_board_name_map.contains(boardId)) {
351 const QString px4Url{"http://px4-travis.s3.amazonaws.com/Firmware/%1/%2.px4"};
352
353 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, StableFirmware, DefaultVehicleFirmware), px4Url.arg("stable").arg(px4_board_name_map.value(boardId)));
354 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, BetaFirmware, DefaultVehicleFirmware), px4Url.arg("beta").arg(px4_board_name_map.value(boardId)));
355 _rgFirmwareDynamic.insert(FirmwareIdentifier(AutoPilotStackPX4, DeveloperFirmware, DefaultVehicleFirmware), px4Url.arg("master").arg(px4_board_name_map.value(boardId)));
356 }
357 break;
358 }
359
360 return &_rgFirmwareDynamic;
361}
362
363void FirmwareUpgradeController::_getFirmwareFile(FirmwareIdentifier firmwareId)
364{
365 QHash<FirmwareIdentifier, QString>* prgFirmware = _firmwareHashForBoardId(static_cast<int>(_bootloaderBoardID));
366 if (firmwareId.firmwareType == CustomFirmware) {
367 _firmwareFilename = QString();
368 _errorCancel(tr("Custom firmware selected but no filename given."));
369 } else {
370 if (prgFirmware->contains(firmwareId)) {
371 _firmwareFilename = prgFirmware->value(firmwareId);
372 } else {
373 _errorCancel(tr("Unable to find specified firmware for board type"));
374 return;
375 }
376 }
377
378 if (_firmwareFilename.isEmpty()) {
379 _errorCancel(tr("No firmware file selected"));
380 } else {
381 _downloadFirmware();
382 }
383}
384
386void FirmwareUpgradeController::_downloadFirmware(void)
387{
388 Q_ASSERT(!_firmwareFilename.isEmpty());
389
390 _flashCancelled = false;
391
392 const bool isRemote = _firmwareFilename.startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
393 if (isRemote) {
394 _appendStatusLog(tr("Downloading firmware from %1").arg(_firmwareFilename));
395 } else {
396 _appendStatusLog(tr("Using firmware file %1").arg(_firmwareFilename));
397 }
398
399 QGCFileDownload* downloader = new QGCFileDownload(this);
400 _activeDownloader = downloader;
401 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
402 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_firmwareDownloadComplete);
403 connect(downloader, &QGCFileDownload::downloadProgress, this, &FirmwareUpgradeController::_firmwareDownloadProgress);
404 if (!downloader->start(_firmwareFilename)) {
405 downloader->deleteLater();
406 _activeDownloader = nullptr;
407 _errorCancel(downloader->errorString());
408 return;
409 }
410}
411
413void FirmwareUpgradeController::_firmwareDownloadProgress(qint64 curr, qint64 total)
414{
415 // Take care of cases where 0 / 0 is emitted as error return value
416 if (total > 0) {
417 _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
418 }
419}
420
422void FirmwareUpgradeController::_firmwareDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
423{
424 _activeDownloader = nullptr;
425
426 if (_flashCancelled) {
427 // User cancelled while the download was in flight; ignore late completion.
428 return;
429 }
430
431 if (success) {
432 const bool isRemote = _firmwareFilename.startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
433 if (isRemote) {
434 _appendStatusLog(tr("Download complete"));
435 }
436
437 FirmwareImage* image = new FirmwareImage(this);
438
439 connect(image, &FirmwareImage::statusMessage, this, &FirmwareUpgradeController::_status);
440 connect(image, &FirmwareImage::errorMessage, this, &FirmwareUpgradeController::_error);
441
442 if (!image->load(localFile, _bootloaderBoardID)) {
443 _errorCancel(tr("Image load failed"));
444 return;
445 }
446
447 // We can't proceed unless we have the bootloader
448 if (!_bootloaderFound) {
449 _errorCancel(tr("Bootloader not found"));
450 return;
451 }
452
453 if (_bootloaderBoardFlashSize != 0 && image->imageSize() > _bootloaderBoardFlashSize) {
454 _errorCancel(tr("Image size of %1 is too large for board flash size %2").arg(image->imageSize()).arg(_bootloaderBoardFlashSize));
455 return;
456 }
457
458 _threadController->flash(image);
459 } else if (!errorMsg.isEmpty()) {
460 _errorCancel(errorMsg);
461 }
462}
463
466{
467 switch (type) {
468 case StableFirmware:
469 return "stable";
471 return "developer";
472 case BetaFirmware:
473 return "beta";
474 default:
475 return "custom";
476 }
477}
478
481void FirmwareUpgradeController::_flashComplete(void)
482{
483 delete _image;
484 _image = nullptr;
485
486 _appendStatusLog(tr("Upgrade complete"), true);
487 emit flashComplete();
489}
490
491void FirmwareUpgradeController::_error(const QString& errorString)
492{
493 delete _image;
494 _image = nullptr;
495
496 _errorCancel(QString("Error: %1").arg(errorString));
497}
498
499void FirmwareUpgradeController::_status(const QString& statusString)
500{
501 _appendStatusLog(statusString);
502}
503
505void FirmwareUpgradeController::_updateProgress(int curr, int total)
506{
507 // Take care of cases where 0 / 0 is emitted as error return value
508 if (total > 0) {
509 _progressBar->setProperty("value", static_cast<float>(curr) / static_cast<float>(total));
510 }
511}
512
514void FirmwareUpgradeController::_eraseProgressTick(void)
515{
516 _eraseTickCount++;
517 _progressBar->setProperty("value", static_cast<float>(_eraseTickCount*_eraseTickMsec) / static_cast<float>(_eraseTotalMsec));
518}
519
521void FirmwareUpgradeController::_appendStatusLog(const QString& text, bool critical)
522{
523 Q_ASSERT(_statusLog);
524
525 QString varText;
526
527 if (critical) {
528 varText = QString("<b>%1</b>").arg(text);
529 } else {
530 varText = text;
531 }
532
533 QMetaObject::invokeMethod(_statusLog,
534 "append",
535 Q_ARG(QString, varText));
536}
537
538void FirmwareUpgradeController::_errorCancel(const QString& msg)
539{
540 _appendStatusLog(msg, false);
541 _appendStatusLog(tr("Upgrade cancelled"), true);
542 emit error();
543 cancel();
545}
546
547void FirmwareUpgradeController::_eraseStarted(void)
548{
549 // We set up our own progress bar for erase since the erase command does not provide one
550 _eraseTickCount = 0;
551 _eraseTimer.start(_eraseTickMsec);
552 emit eraseStarted();
553}
554
555void FirmwareUpgradeController::_eraseComplete(void)
556{
557 _eraseTimer.stop();
558}
559
561{
562 _selectedFirmwareBuildType = firmwareType;
563 emit selectedFirmwareBuildTypeChanged(_selectedFirmwareBuildType);
564 _buildAPMFirmwareNames();
565}
566
567void FirmwareUpgradeController::_buildAPMFirmwareNames(void)
568{
570 return;
571 }
572
573 bool chibios = _apmChibiOSSetting->rawValue().toInt() == 0;
574 FirmwareVehicleType_t vehicleType = static_cast<FirmwareVehicleType_t>(_apmVehicleTypeSetting->rawValue().toInt());
575 QString boardDescription = _boardInfo.description();
576 quint16 boardVID = _boardInfo.vendorIdentifier();
577 quint16 boardPID = _boardInfo.productIdentifier();
578 uint32_t rawBoardId = _bootloaderBoardID == Bootloader::boardIDPX4FMUV3 ? Bootloader::boardIDPX4FMUV2 : _bootloaderBoardID;
579
580 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);
581
582 _apmFirmwareNames.clear();
583 _apmFirmwareNamesBestIndex = -1;
584 _apmFirmwareUrls.clear();
585
586 QString apmDescriptionSuffix("-BL");
587 QString boardDescriptionPrefix;
588 bool bootloaderMatch = boardDescription.endsWith(apmDescriptionSuffix);
589
590 int currentIndex = 0;
591 for (const ManifestFirmwareInfo_t& firmwareInfo: _rgManifestFirmwareInfo) {
592 bool match = false;
593 if (firmwareInfo.firmwareBuildType == _selectedFirmwareBuildType && firmwareInfo.chibios == chibios && firmwareInfo.vehicleType == vehicleType && firmwareInfo.boardId == rawBoardId) {
594 if (firmwareInfo.fmuv2 && _bootloaderBoardID == Bootloader::boardIDPX4FMUV3) {
595 qCDebug(FirmwareUpgradeControllerLog) << "Skipping fmuv2 manifest entry for fmuv3 board:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
596 } else {
597 qCDebug(FirmwareUpgradeControllerLog) << "Board id match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
598 match = true;
599 if (bootloaderMatch && _apmFirmwareNamesBestIndex == -1 && firmwareInfo.rgBootloaderPortString.contains(boardDescription)) {
600 _apmFirmwareNamesBestIndex = currentIndex;
601 qCDebug(FirmwareUpgradeControllerLog) << "Bootloader best match:" << firmwareInfo.friendlyName << boardDescription << firmwareInfo.rgBootloaderPortString << firmwareInfo.url << firmwareInfo.vehicleType;
602 }
603 }
604 }
605
606 if (match) {
607 _apmFirmwareNames.append(firmwareInfo.friendlyName);
608 _apmFirmwareUrls.append(firmwareInfo.url);
609 currentIndex++;
610 }
611 }
612
613 if (_apmFirmwareNamesBestIndex == -1) {
614 _apmFirmwareNamesBestIndex++;
615 if (_apmFirmwareNames.length() > 1) {
616 _apmFirmwareNames.prepend(tr("Choose board type"));
617 _apmFirmwareUrls.prepend(QString());
618 }
619 }
620
622}
623
625{
626 if (index < 0 || index >= _apmVehicleTypeFromCurrentVersionList.length()) {
627 qWarning() << "Invalid index, index:count" << index << _apmVehicleTypeFromCurrentVersionList.length();
628 return CopterFirmware;
629 }
630
631 return _apmVehicleTypeFromCurrentVersionList[index];
632}
633
634void FirmwareUpgradeController::_determinePX4StableVersion(void)
635{
636 QGCFileDownload* downloader = new QGCFileDownload(this);
637 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
638 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete);
639 if (!downloader->start(QStringLiteral("https://api.github.com/repos/PX4/Firmware/releases"))) {
640 qCWarning(FirmwareUpgradeControllerLog) << "PX4 releases github download did not start:" << downloader->errorString();
641 downloader->deleteLater();
642 return;
643 }
644}
645
646void FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
647{
648 if (success) {
649 QFile jsonFile(localFile);
650 if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
651 qCWarning(FirmwareUpgradeControllerLog) << "Unable to open github px4 releases json file" << localFile << jsonFile.errorString();
652 return;
653 }
654 QByteArray bytes = jsonFile.readAll();
655 jsonFile.close();
656
657 QJsonParseError jsonParseError;
658 QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
659 if (jsonParseError.error != QJsonParseError::NoError) {
660 qCWarning(FirmwareUpgradeControllerLog) << "Unable to open px4 releases json document" << localFile << jsonParseError.errorString();
661 return;
662 }
663
664 // Json should be an array of release objects
665 if (!doc.isArray()) {
666 qCWarning(FirmwareUpgradeControllerLog) << "px4 releases json document is not an array" << localFile;
667 return;
668 }
669 QJsonArray releases = doc.array();
670
671 // The first release marked prerelease=false is stable
672 // The first release marked prerelease=true is beta
673 bool foundStable = false;
674 bool foundBeta = false;
675 for (int i=0; i<releases.count() && (!foundStable || !foundBeta); i++) {
676 QJsonObject release = releases[i].toObject();
677 if (!foundStable && !release["prerelease"].toBool()) {
678 _px4StableVersion = release["name"].toString();
679 emit px4StableVersionChanged(_px4StableVersion);
680 qCDebug(FirmwareUpgradeControllerLog()) << "Found px4 stable version" << _px4StableVersion;
681 foundStable = true;
682 } else if (!foundBeta && release["prerelease"].toBool()) {
683 _px4BetaVersion = release["name"].toString();
684 emit px4BetaVersionChanged(_px4BetaVersion);
685 qCDebug(FirmwareUpgradeControllerLog()) << "Found px4 beta version" << _px4BetaVersion;
686 foundBeta = true;
687 }
688 }
689
690 if (!foundStable) {
691 qCDebug(FirmwareUpgradeControllerLog()) << "Unable to find px4 stable version" << localFile;
692 }
693 if (!foundBeta) {
694 qCDebug(FirmwareUpgradeControllerLog()) << "Unable to find px4 beta version" << localFile;
695 }
696 } else if (!errorMsg.isEmpty()) {
697 qCWarning(FirmwareUpgradeControllerLog) << "PX4 releases github download failed" << errorMsg;
698 }
699}
700
701void FirmwareUpgradeController::_downloadArduPilotManifest(void)
702{
703 _downloadingFirmwareList = true;
705
706 QGCFileDownload* downloader = new QGCFileDownload(this);
707 connect(downloader, &QGCFileDownload::finished, downloader, &QObject::deleteLater);
708 connect(downloader, &QGCFileDownload::finished, this, &FirmwareUpgradeController::_ardupilotManifestDownloadComplete);
709 // Use autoDecompress to stream-decompress directly during download
710 downloader->setAutoDecompress(true);
711 if (!downloader->start(QStringLiteral("https://firmware.ardupilot.org/manifest.json.gz"))) {
712 qCWarning(FirmwareUpgradeControllerLog) << "ArduPilot Manifest download did not start:" << downloader->errorString();
713 downloader->deleteLater();
714 _downloadingFirmwareList = false;
716 }
717}
718
719void FirmwareUpgradeController::_ardupilotManifestDownloadComplete(bool success, const QString &localFile, const QString &errorMsg)
720{
721 const auto clearDownloadState = [this]() {
722 if (_downloadingFirmwareList) {
723 _downloadingFirmwareList = false;
725 }
726 };
727
728 if (success) {
729 qCDebug(FirmwareUpgradeControllerLog) << "_ardupilotManifestDownloadFinished" << localFile;
730
731 // localFile is already decompressed (autoDecompress=true streams directly to .json)
732 QString errorString;
733 QJsonDocument doc;
734 if (!JsonParsing::isJsonFile(localFile, doc, errorString)) {
735 qCWarning(FirmwareUpgradeControllerLog) << "Json file read failed" << errorString;
736 clearDownloadState();
737 return;
738 }
739
740 QJsonObject json = doc.object();
741 QJsonArray rgFirmware = json[_manifestFirmwareJsonKey].toArray();
742
743 for (int i=0; i<rgFirmware.count(); i++) {
744 const QJsonObject& firmwareJson = rgFirmware[i].toObject();
745
746 FirmwareVehicleType_t firmwareVehicleType = _manifestMavTypeToFirmwareVehicleType(firmwareJson[_manifestMavTypeJsonKey].toString());
747 FirmwareBuildType_t firmwareBuildType = _manifestMavFirmwareVersionTypeToFirmwareBuildType(firmwareJson[_manifestMavFirmwareVersionTypeJsonKey].toString());
748 QString format = firmwareJson[_manifestFormatJsonKey].toString();
749 QString platform = firmwareJson[_manifestPlatformKey].toString();
750
751 if (firmwareVehicleType != DefaultVehicleFirmware && firmwareBuildType != CustomFirmware && (format == QStringLiteral("apj") || format == QStringLiteral("px4"))) {
752 if (platform.contains("-heli") && firmwareVehicleType != HeliFirmware) {
753 continue;
754 }
755
756 _rgManifestFirmwareInfo.append(ManifestFirmwareInfo_t());
757 ManifestFirmwareInfo_t& firmwareInfo = _rgManifestFirmwareInfo.last();
758
759 firmwareInfo.boardId = static_cast<uint32_t>(firmwareJson[_manifestBoardIdJsonKey].toInt());
760 firmwareInfo.firmwareBuildType = firmwareBuildType;
761 firmwareInfo.vehicleType = firmwareVehicleType;
762 firmwareInfo.url = firmwareJson[_manifestUrlJsonKey].toString();
763 firmwareInfo.version = firmwareJson[_manifestMavFirmwareVersionJsonKey].toString();
764 firmwareInfo.chibios = format == QStringLiteral("apj"); firmwareInfo.fmuv2 = platform.contains(QStringLiteral("fmuv2"));
765
766 QJsonArray bootloaderArray = firmwareJson[_manifestBootloaderStrJsonKey].toArray();
767 for (int j=0; j<bootloaderArray.count(); j++) {
768 firmwareInfo.rgBootloaderPortString.append(bootloaderArray[j].toString());
769 }
770
771 QJsonArray usbidArray = firmwareJson[_manifestUSBIDJsonKey].toArray();
772 for (int j=0; j<usbidArray.count(); j++) {
773 QStringList vidpid = usbidArray[j].toString().split('/');
774 QString vid = vidpid[0];
775 QString pid = vidpid[1];
776
777 bool ok;
778 firmwareInfo.rgVID.append(vid.right(vid.length() - 2).toInt(&ok, 16));
779 firmwareInfo.rgPID.append(pid.right(pid.length() - 2).toInt(&ok, 16));
780 }
781
782 QString brandName = firmwareJson[_manifestBrandNameKey].toString();
783 firmwareInfo.friendlyName = QStringLiteral("%1 - %2").arg(brandName.isEmpty() ? platform : brandName).arg(firmwareInfo.version);
784 }
785 }
786
787 if (_bootloaderFound) {
788 _buildAPMFirmwareNames();
789 }
790
791 clearDownloadState();
792 } else if (!errorMsg.isEmpty()) {
793 qCWarning(FirmwareUpgradeControllerLog) << "ArduPilot Manifest download failed" << errorMsg;
794 clearDownloadState();
795 }
796}
797
798FirmwareUpgradeController::FirmwareBuildType_t FirmwareUpgradeController::_manifestMavFirmwareVersionTypeToFirmwareBuildType(const QString& manifestMavFirmwareVersionType)
799{
800 if (_manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap.contains(manifestMavFirmwareVersionType)) {
801 return _manifestMavFirmwareVersionTypeToFirmwareBuildTypeMap[manifestMavFirmwareVersionType];
802 } else {
803 return CustomFirmware;
804 }
805}
806
807FirmwareUpgradeController::FirmwareVehicleType_t FirmwareUpgradeController::_manifestMavTypeToFirmwareVehicleType(const QString& manifestMavType)
808{
809 if (_manifestMavTypeToFirmwareVehicleTypeMap.contains(manifestMavType)) {
810 return _manifestMavTypeToFirmwareVehicleTypeMap[manifestMavType];
811 } else {
813 }
814}
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:90
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:82
static LinkManager * instance()
void setConnectionsSuspended(const QString &reason)
Definition LinkManager.h:79
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