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