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