QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FirmwareUpgrade.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtQuick.Layouts
5
6import QGroundControl
7import QGroundControl.Controls
8import QGroundControl.FactControls
9
10SetupPage {
11 id: firmwarePage
12 pageComponent: firmwarePageComponent
13 pageName: qsTr("Firmware")
14 showAdvanced: globals.activeVehicle && globals.activeVehicle.apmFirmware
15
16 Component {
17 id: firmwarePageComponent
18
19 ColumnLayout {
20 width: availableWidth
21 height: availableHeight
22 spacing: ScreenTools.defaultFontPixelHeight
23
24 // Those user visible strings are hard to translate because we can't send the
25 // HTML strings to translation as this can create a security risk. we need to find
26 // a better way to highlight them, or use less highlights.
27
28 // User visible strings
29 readonly property string title: qsTr("Firmware Setup") // Popup dialog title
30 readonly property string highlightPrefix: "<font color=\"" + qgcPal.warningText + "\">"
31 readonly property string highlightSuffix: "</font>"
32 readonly property string welcomeText: qsTr("%1 can upgrade the firmware on Pixhawk devices and SiK Radios.").arg(QGroundControl.appName)
33 readonly property string welcomeTextSingle: qsTr("Update the autopilot firmware to the latest version")
34 readonly property string plugInText: highlightPrefix + qsTr("Plug in your device") + highlightSuffix + qsTr(" via USB, then select it below and press ") + highlightPrefix + qsTr("Flash") + highlightSuffix + "."
35 readonly property string unplugReplugText: highlightPrefix + qsTr("Now unplug your device and plug it back in to enter bootloader mode.") + highlightSuffix
36 readonly property string flashFailText: qsTr("If upgrade failed, make sure to connect ") + highlightPrefix + qsTr("directly") + highlightSuffix + qsTr(" to a powered USB port on your computer, not through a USB hub. ") +
37 qsTr("Also make sure you are only powered via USB ") + highlightPrefix + qsTr("not battery") + highlightSuffix + "."
38
39 readonly property int _defaultFimwareTypePX4: 12
40 readonly property int _defaultFimwareTypeAPM: 3
41
42 readonly property int _boardTypePixhawk: 0
43 readonly property int _boardTypeSiKRadio: 1
44
45 property var _firmwareUpgradeSettings: QGroundControl.settingsManager.firmwareUpgradeSettings
46 property var _defaultFirmwareFact: _firmwareUpgradeSettings.defaultFirmwareType
47 property bool _defaultFirmwareIsPX4: true
48
49 property string firmwareWarningMessage
50 property bool firmwareWarningMessageVisible: false
51 property string firmwareName
52 property bool _flashStarted: false ///< true: user has clicked Flash, suppress further preselection
53 property bool _cancellable: true ///< false once erase has started — past the point of clean cancellation
54 property string _selectedSystemLocation
55 property string _selectedDisplayName ///< snapshot of chosen port's label, used while flashing
56
57 property bool _singleFirmwareMode: QGroundControl.corePlugin.options.firmwareUpgradeSingleURL.length != 0 ///< true: running in special single firmware download mode
58
59 function setupPageCompleted() {
60 controller.startBoardSearch()
61 _defaultFirmwareIsPX4 = _defaultFirmwareFact.rawValue === _defaultFimwareTypePX4 // we don't want this to be bound and change as radios are selected
62 _lastKnownCount = 0
63 }
64
65 property int _lastKnownCount: 0
66
67 function _recognizedBoardCount() {
68 var ports = controller.availablePorts
69 var count = 0
70 for (var i = 0; i < ports.length; i++) {
71 if (ports[i].boardType === _boardTypePixhawk || ports[i].boardType === _boardTypeSiKRadio) {
72 count++
73 }
74 }
75 return count
76 }
77
78 function _preselectIndex() {
79 var ports = controller.availablePorts
80 if (ports.length === 0) {
81 return -1
82 }
83 for (var i = 0; i < ports.length; i++) {
84 if (ports[i].boardType === _boardTypePixhawk) {
85 return i
86 }
87 }
88 for (var j = 0; j < ports.length; j++) {
89 if (ports[j].boardType === _boardTypeSiKRadio) {
90 return j
91 }
92 }
93 // No recognized device found
94 return -1
95 }
96
97 function _refreshSelection() {
98 if (_flashStarted || portCombo.popup.visible) {
99 return
100 }
101 var knownCount = _recognizedBoardCount()
102 if (knownCount > 1 && _lastKnownCount <= 1) {
103 statusTextArea.append(highlightPrefix + qsTr("Multiple devices detected. Make sure to select the correct one from the list.") + highlightSuffix)
104 }
105 _lastKnownCount = knownCount
106 var ports = controller.availablePorts
107 if (ports.length === 0) {
108 portCombo.currentIndex = -1
109 _selectedSystemLocation = ""
110 return
111 }
112 if (_selectedSystemLocation !== "") {
113 // Check if the current selection is already a recognized device
114 var currentIsRecognized = false
115 for (var i = 0; i < ports.length; i++) {
116 if (ports[i].systemLocation === _selectedSystemLocation) {
117 if (ports[i].boardType === _boardTypePixhawk || ports[i].boardType === _boardTypeSiKRadio) {
118 currentIsRecognized = true
119 }
120 break
121 }
122 }
123 // Only keep the current selection if it is a recognized device;
124 // otherwise fall through to preselect the first recognized device
125 if (currentIsRecognized) {
126 for (var j = 0; j < ports.length; j++) {
127 if (ports[j].systemLocation === _selectedSystemLocation) {
128 portCombo.currentIndex = j
129 return
130 }
131 }
132 }
133 }
134 portCombo.currentIndex = _preselectIndex()
135 if (portCombo.currentIndex >= 0) {
136 _selectedSystemLocation = ports[portCombo.currentIndex].systemLocation
137 }
138 }
139
140
141 FirmwareUpgradeController {
142 id: controller
143 progressBar: progressBar
144 statusLog: statusTextArea
145
146 property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
147
148 onActiveVehicleChanged: {
149 if (!globals.activeVehicle && !_flashStarted) {
150 statusTextArea.append(plugInText)
151 }
152 }
153
154 onAvailablePortsChanged: _refreshSelection()
155
156 onBoardGone: {
157 if (_flashStarted) {
158 statusTextArea.append(highlightPrefix + qsTr("Device disconnected — waiting for it to reappear in bootloader mode...") + highlightSuffix)
159 }
160 }
161
162 onBoardFound: {
163 if (_flashStarted) {
164 statusTextArea.append(highlightPrefix + qsTr("Found device") + highlightSuffix + ": " + controller.boardType + " (" + controller.boardPort + ")")
165 if (QGroundControl.multiVehicleManager.activeVehicle) {
166 QGroundControl.multiVehicleManager.activeVehicle.vehicleLinkManager.autoDisconnect = true
167 }
168 }
169 }
170
171 onShowFirmwareSelectDlg: firmwareSelectDialogFactory.open()
172 onEraseStarted: _cancellable = false
173 onError: {
174 statusTextArea.append(flashFailText)
175 _flashStarted = false
176 _cancellable = true
177 }
178 onFlashComplete: {
179 _flashStarted = false
180 _cancellable = true
181 }
182 }
183
184 QGCPopupDialogFactory {
185 id: firmwareSelectDialogFactory
186
187 dialogComponent: firmwareSelectDialogComponent
188 }
189
190 Component {
191 id: firmwareSelectDialogComponent
192
193 QGCPopupDialog {
194 id: firmwareSelectDialog
195 title: qsTr("Firmware Setup")
196 buttons: Dialog.Ok | Dialog.Cancel
197
198 property bool showFirmwareTypeSelection: _advanced.checked
199
200 QGCFileDialog {
201 id: customFirmwareDialog
202 title: qsTr("Select Firmware File")
203 nameFilters: [qsTr("Firmware Files (*.px4 *.apj *.bin *.ihx)"), qsTr("All Files (*)")]
204 folder: QGroundControl.settingsManager.appSettings.logSavePath
205 onAcceptedForLoad: (file) => {
206 controller.flashFirmwareUrl(file)
207 close()
208 firmwareSelectDialog.close()
209 }
210 }
211
212 function firmwareVersionChanged(model) {
213 firmwareWarningMessageVisible = false
214 // All of this bizarre, setting model to null and index to 1 and then to 0 is to work around
215 // strangeness in the combo box implementation. This sequence of steps correctly changes the combo model
216 // without generating any warnings and correctly updates the combo text with the new selection.
217 firmwareBuildTypeCombo.model = null
218 firmwareBuildTypeCombo.model = model
219 firmwareBuildTypeCombo.currentIndex = 1
220 firmwareBuildTypeCombo.currentIndex = 0
221 }
222
223 function updatePX4VersionDisplay() {
224 var versionString = ""
225 if (_advanced.checked) {
226 switch (controller.selectedFirmwareBuildType) {
227 case FirmwareUpgradeController.StableFirmware:
228 versionString = controller.px4StableVersion
229 break
230 case FirmwareUpgradeController.BetaFirmware:
231 versionString = controller.px4BetaVersion
232 break
233 }
234 } else {
235 versionString = controller.px4StableVersion
236 }
237 px4FlightStackRadio.text = qsTr("PX4 Pro ") + versionString
238 //px4FlightStackRadio2.text = qsTr("PX4 Pro ") + versionString
239 }
240
241 Component.onCompleted: {
242 firmwarePage.advanced = false
243 firmwarePage.showAdvanced = false
244 updatePX4VersionDisplay()
245 }
246
247 Connections {
248 target: controller
249 onError: reject()
250 }
251
252 onAccepted: {
253 if (_singleFirmwareMode) {
254 if (controller.selectedFirmwareBuildType === FirmwareUpgradeController.CustomFirmware) {
255 // User picked Custom but cancelled the auto-opened picker; reopen it
256 // and keep this dialog open until a file is chosen or Cancel is clicked.
257 customFirmwareDialog.openForLoad()
258 firmwareSelectDialog.preventClose = true
259 } else {
260 controller.flashSingleFirmwareMode(controller.selectedFirmwareBuildType)
261 }
262 } else {
263 var firmwareBuildType = firmwareBuildTypeCombo.model.get(firmwareBuildTypeCombo.currentIndex).firmwareType
264 var vehicleType = FirmwareUpgradeController.DefaultVehicleFirmware
265
266 var stack = apmFlightStack.checked ? FirmwareUpgradeController.AutoPilotStackAPM : FirmwareUpgradeController.AutoPilotStackPX4
267 if (apmFlightStack.checked) {
268 if (firmwareBuildType === FirmwareUpgradeController.CustomFirmware) {
269 vehicleType = apmVehicleTypeCombo.currentIndex
270 } else {
271 if (controller.apmFirmwareNames.length === 0) {
272 // Not ready yet, or no firmware available
273 QGroundControl.showMessageDialog(firmwarePage, firmwareSelectDialog.title, qsTr("Either firmware list is still downloading, or no firmware is available for current selection."))
274 firmwareSelectDialog.preventClose = true
275 return
276 }
277 if (ardupilotFirmwareSelectionCombo.currentIndex == -1) {
278 QGroundControl.showMessageDialog(firmwarePage, firmwareSelectDialog.title, qsTr("You must choose a board type."))
279 firmwareSelectDialog.preventClose = true
280 return
281 }
282
283 var firmwareUrl = controller.apmFirmwareUrls[ardupilotFirmwareSelectionCombo.currentIndex]
284 if (firmwareUrl == "") {
285 QGroundControl.showMessageDialog(firmwarePage, firmwareSelectDialog.title, qsTr("No firmware was found for the current selection."))
286 firmwareSelectDialog.preventClose = true
287 return
288 }
289 controller.flashFirmwareUrl(controller.apmFirmwareUrls[ardupilotFirmwareSelectionCombo.currentIndex])
290 return
291 }
292 }
293 //-- If custom, get file path
294 if (firmwareBuildType === FirmwareUpgradeController.CustomFirmware) {
295 customFirmwareDialog.openForLoad()
296 } else {
297 controller.flash(stack, firmwareBuildType, vehicleType)
298 }
299 }
300 }
301
302 function reject() {
303 statusTextArea.append(highlightPrefix + qsTr("Upgrade cancelled") + highlightSuffix)
304 controller.cancel()
305 close()
306 }
307
308 ListModel {
309 id: firmwareBuildTypeList
310
311 ListElement {
312 text: qsTr("Standard Version (stable)")
313 firmwareType: FirmwareUpgradeController.StableFirmware
314 }
315 ListElement {
316 text: qsTr("Beta Testing (beta)")
317 firmwareType: FirmwareUpgradeController.BetaFirmware
318 }
319 ListElement {
320 text: qsTr("Developer Build (master)")
321 firmwareType: FirmwareUpgradeController.DeveloperFirmware
322 }
323 ListElement {
324 text: qsTr("Custom firmware file...")
325 firmwareType: FirmwareUpgradeController.CustomFirmware
326 }
327 }
328
329 ListModel {
330 id: singleFirmwareModeTypeList
331
332 ListElement {
333 text: qsTr("Standard Version")
334 firmwareType: FirmwareUpgradeController.StableFirmware
335 }
336 ListElement {
337 text: qsTr("Custom firmware file...")
338 firmwareType: FirmwareUpgradeController.CustomFirmware
339 }
340 }
341
342 ColumnLayout {
343 width: Math.max(ScreenTools.defaultFontPixelWidth * 40, firmwareRadiosColumn.width)
344 spacing: globals.defaultTextHeight / 2
345
346 QGCLabel {
347 Layout.fillWidth: true
348 wrapMode: Text.WordWrap
349 text: (_singleFirmwareMode || !QGroundControl.apmFirmwareSupported) ? _singleFirmwareLabel : _pixhawkLabel
350
351 readonly property string _pixhawkLabel: qsTr("Detected Pixhawk board. You can select from the following flight stacks:")
352 readonly property string _singleFirmwareLabel: qsTr("Press Ok to upgrade your vehicle.")
353 }
354
355 Column {
356 id: firmwareRadiosColumn
357 spacing: 0
358
359 visible: !_singleFirmwareMode && QGroundControl.apmFirmwareSupported
360
361 Component.onCompleted: {
362 if(!QGroundControl.apmFirmwareSupported) {
363 _defaultFirmwareFact.rawValue = _defaultFimwareTypePX4
364 firmwareVersionChanged(firmwareBuildTypeList)
365 }
366 }
367
368 QGCRadioButton {
369 id: px4FlightStackRadio
370 text: qsTr("PX4 Pro ")
371 font.bold: _defaultFirmwareIsPX4
372 checked: _defaultFirmwareIsPX4
373
374 onClicked: {
375 _defaultFirmwareFact.rawValue = _defaultFimwareTypePX4
376 firmwareVersionChanged(firmwareBuildTypeList)
377 }
378 }
379
380 QGCRadioButton {
381 id: apmFlightStack
382 text: qsTr("ArduPilot")
383 font.bold: !_defaultFirmwareIsPX4
384 checked: !_defaultFirmwareIsPX4
385
386 onClicked: {
387 _defaultFirmwareFact.rawValue = _defaultFimwareTypeAPM
388 firmwareVersionChanged(firmwareBuildTypeList)
389 }
390 }
391 }
392
393 FactComboBox {
394 Layout.fillWidth: true
395 visible: apmFlightStack.checked
396 fact: _firmwareUpgradeSettings.apmChibiOS
397 indexModel: false
398 }
399
400 FactComboBox {
401 id: apmVehicleTypeCombo
402 Layout.fillWidth: true
403 visible: apmFlightStack.checked
404 fact: _firmwareUpgradeSettings.apmVehicleType
405 indexModel: false
406 }
407
408 QGCComboBox {
409 id: ardupilotFirmwareSelectionCombo
410 Layout.fillWidth: true
411 visible: apmFlightStack.checked && !controller.downloadingFirmwareList && controller.apmFirmwareNames.length !== 0
412 model: controller.apmFirmwareNames
413 onModelChanged: currentIndex = controller.apmFirmwareNamesBestIndex
414 }
415
416 QGCLabel {
417 Layout.fillWidth: true
418 wrapMode: Text.WordWrap
419 text: qsTr("Downloading list of available firmwares...")
420 visible: controller.downloadingFirmwareList
421 }
422
423 QGCLabel {
424 Layout.fillWidth: true
425 wrapMode: Text.WordWrap
426 text: qsTr("No Firmware Available")
427 visible: !controller.downloadingFirmwareList && (QGroundControl.apmFirmwareSupported && controller.apmFirmwareNames.length === 0)
428 }
429
430 QGCCheckBox {
431 id: _advanced
432 text: qsTr("Advanced settings")
433 checked: false
434
435 onClicked: {
436 firmwareBuildTypeCombo.currentIndex = 0
437 firmwareWarningMessageVisible = false
438 updatePX4VersionDisplay()
439 }
440 }
441
442 QGCLabel {
443 Layout.fillWidth: true
444 wrapMode: Text.WordWrap
445 visible: showFirmwareTypeSelection
446 text: _singleFirmwareMode ? qsTr("Select the standard version or one from the file system (previously downloaded):") :
447 qsTr("Select which version of the above flight stack you would like to install:")
448 }
449
450 QGCComboBox {
451 id: firmwareBuildTypeCombo
452 Layout.fillWidth: true
453 visible: showFirmwareTypeSelection
454 textRole: "text"
455 model: _singleFirmwareMode ? singleFirmwareModeTypeList : firmwareBuildTypeList
456
457 onActivated: (index) => {
458 var fwType = model.get(index).firmwareType
459 controller.selectedFirmwareBuildType = fwType
460 if (fwType === FirmwareUpgradeController.BetaFirmware) {
461 firmwareWarningMessageVisible = true
462 firmwareVersionWarningLabel.text = qsTr("WARNING: BETA FIRMWARE. ") +
463 qsTr("This firmware version is ONLY intended for beta testers. ") +
464 qsTr("Although it has received FLIGHT TESTING, it represents actively changed code. ") +
465 qsTr("Do NOT use for normal operation.")
466 } else if (fwType === FirmwareUpgradeController.DeveloperFirmware) {
467 firmwareWarningMessageVisible = true
468 firmwareVersionWarningLabel.text = qsTr("WARNING: CONTINUOUS BUILD FIRMWARE. ") +
469 qsTr("This firmware has NOT BEEN FLIGHT TESTED. ") +
470 qsTr("It is only intended for DEVELOPERS. ") +
471 qsTr("Run bench tests without props first. ") +
472 qsTr("Do NOT fly this without additional safety precautions. ") +
473 qsTr("Follow the forums actively when using it.")
474 } else {
475 firmwareWarningMessageVisible = false
476 }
477 updatePX4VersionDisplay()
478 if (fwType === FirmwareUpgradeController.CustomFirmware) {
479 customFirmwareDialog.openForLoad()
480 }
481 }
482 }
483
484 QGCLabel {
485 id: firmwareVersionWarningLabel
486 Layout.fillWidth: true
487 wrapMode: Text.WordWrap
488 visible: firmwareWarningMessageVisible
489 }
490 } // ColumnLayout
491 } // QGCPopupDialog
492 } // Component - firmwareSelectDialogComponent
493
494 RowLayout {
495 spacing: ScreenTools.defaultFontPixelWidth
496 visible: !flashBootloaderButton.visible
497
498 QGCComboBox {
499 id: portCombo
500 sizeToContents: true
501 visible: !_flashStarted
502 enabled: controller.availablePorts.length > 0
503 model: controller.availablePorts
504 textRole: "displayName"
505
506 alternateText: {
507 if (_lastKnownCount !== 1) {
508 return ""
509 }
510 var idx = portCombo.currentIndex
511 var ports = controller.availablePorts
512 if (idx < 0 || idx >= ports.length) {
513 return ""
514 }
515 var p = ports[idx]
516 if (p.boardName && p.description) {
517 return p.description + " [" + p.boardName + "]"
518 }
519 return p.boardName || p.description || p.displayName
520 }
521
522 onActivated: (index) => {
523 if (index >= 0 && index < controller.availablePorts.length) {
524 _selectedSystemLocation = controller.availablePorts[index].systemLocation
525 }
526 }
527 }
528
529 QGCLabel {
530 id: portLabel
531 visible: _flashStarted
532 text: qsTr("Flashing - %1").arg(_selectedDisplayName)
533 elide: Text.ElideRight
534 }
535
536 QGCButton {
537 id: flashButton
538 text: qsTr("Flash")
539 visible: !_flashStarted
540 enabled: portCombo.currentIndex >= 0 && controller.availablePorts.length > 0
541 onClicked: {
542 var ports = controller.availablePorts
543 if (portCombo.currentIndex < 0 || portCombo.currentIndex >= ports.length) {
544 return
545 }
546 var entry = ports[portCombo.currentIndex]
547 _selectedSystemLocation = entry.systemLocation
548 _selectedDisplayName = portCombo.alternateText !== "" ? portCombo.alternateText : entry.displayName
549 _flashStarted = true
550 statusTextArea.append(unplugReplugText)
551 if (QGroundControl.multiVehicleManager.activeVehicle) {
552 QGroundControl.multiVehicleManager.activeVehicle.vehicleLinkManager.autoDisconnect = true
553 }
554 controller.flashPort(entry.systemLocation)
555 }
556 }
557
558 QGCButton {
559 id: cancelButton
560 text: qsTr("Cancel")
561 visible: _flashStarted
562 enabled: _cancellable
563 onClicked: {
564 controller.cancel()
565 _flashStarted = false
566 _cancellable = true
567 _selectedDisplayName = ""
568 statusTextArea.append(highlightPrefix + qsTr("Cancelled. Select a port and press Flash to try again.") + highlightSuffix)
569 }
570 }
571 }
572
573 ProgressBar {
574 id: progressBar
575 Layout.preferredWidth: parent.width
576 visible: !flashBootloaderButton.visible
577 }
578
579 QGCButton {
580 id: flashBootloaderButton
581 text: qsTr("Flash ChibiOS Bootloader")
582 visible: firmwarePage.advanced
583 onClicked: globals.activeVehicle.flashBootloader()
584 }
585
586 TextArea {
587 id: statusTextArea
588 Layout.preferredWidth: parent.width
589 Layout.fillHeight: true
590 readOnly: true
591 font.pointSize: ScreenTools.defaultFontPointSize
592 textFormat: TextEdit.RichText
593 text: _singleFirmwareMode ? welcomeTextSingle : welcomeText
594 color: qgcPal.text
595
596 background: Rectangle {
597 color: qgcPal.windowShade
598 }
599 }
600
601 } // ColumnLayout
602 } // Component
603} // SetupPage