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