QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
BatteryIndicator.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Layouts
3
4import QGroundControl
5import QGroundControl.Controls
6import QGroundControl.FactControls
7
8//-------------------------------------------------------------------------
9//-- Battery Indicator
10Item {
11 id: control
12 objectName: "toolbar_batteryIndicator"
13 anchors.top: parent.top
14 anchors.bottom: parent.bottom
15 width: batteryIndicatorRow.width
16
17 property bool showIndicator: _activeVehicle && _activeVehicle.batteries.count > 0
18 property bool waitForParameters: false
19 property Component expandedPageComponent
20
21 property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
22 property var _batterySettings: QGroundControl.settingsManager.batteryIndicatorSettings
23 property Fact _indicatorDisplay: _batterySettings.valueDisplay
24 property bool _showPercentage: _indicatorDisplay.rawValue === 0
25 property bool _showVoltage: _indicatorDisplay.rawValue === 1
26 property bool _showBoth: _indicatorDisplay.rawValue === 2
27 property int _lowestBatteryId: -1 // -1: show all batteries, otherwise show only battery with this id
28
29 // Properties to hold the thresholds
30 property int threshold1: _batterySettings.threshold1.rawValue
31 property int threshold2: _batterySettings.threshold2.rawValue
32
33 function _recalcLowestBatteryIdFromVoltage() {
34 if (_activeVehicle) {
35 // If there is only one battery then it is the lowest
36 if (_activeVehicle.batteries.count === 1) {
37 _lowestBatteryId = _activeVehicle.batteries.get(0).id.rawValue
38 return
39 }
40
41 // If we have valid voltage for all batteries we use that to determine lowest battery
42 let allHaveVoltage = true
43 for (var i = 0; i < _activeVehicle.batteries.count; i++) {
44 let battery = _activeVehicle.batteries.get(i)
45 if (isNaN(battery.voltage.rawValue)) {
46 allHaveVoltage = false
47 break
48 }
49 }
50 if (allHaveVoltage) {
51 let lowestBattery = _activeVehicle.batteries.get(0)
52 let lowestBatteryId = lowestBattery.id.rawValue
53 for (var i = 1; i < _activeVehicle.batteries.count; i++) {
54 let battery = _activeVehicle.batteries.get(i)
55 if (battery.voltage.rawValue < lowestBattery.voltage.rawValue) {
56 lowestBattery = battery
57 lowestBatteryId = battery.id.rawValue
58 }
59 }
60 _lowestBatteryId = lowestBatteryId
61 return
62 }
63 }
64
65 // Couldn't determine lowest battery, show all
66 _lowestBatteryId = -1
67 }
68
69 function _recalcLowestBatteryIdFromPercentage() {
70 if (_activeVehicle) {
71 // If there is only one battery then it is the lowest
72 if (_activeVehicle.batteries.count === 1) {
73 _lowestBatteryId = _activeVehicle.batteries.get(0).id.rawValue
74 return
75 }
76
77 // If we have valid percentage for all batteries we use that to determine lowest battery
78 let allHavePercentage = true
79 for (var i = 0; i < _activeVehicle.batteries.count; i++) {
80 let battery = _activeVehicle.batteries.get(i)
81 if (isNaN(battery.percentRemaining.rawValue)) {
82 allHavePercentage = false
83 break
84 }
85 }
86 if (allHavePercentage) {
87 let lowestBattery = _activeVehicle.batteries.get(0)
88 let lowestBatteryId = lowestBattery.id.rawValue
89 for (var i = 1; i < _activeVehicle.batteries.count; i++) {
90 let battery = _activeVehicle.batteries.get(i)
91 if (battery.percentRemaining.rawValue < lowestBattery.percentRemaining.rawValue) {
92 lowestBattery = battery
93 lowestBatteryId = battery.id.rawValue
94 }
95 }
96 _lowestBatteryId = lowestBatteryId
97 return
98 }
99 }
100
101 // Couldn't determine lowest battery, show all
102 _lowestBatteryId = -1
103 }
104
105 function _recalcLowestBatteryIdFromChargeState() {
106 if (_activeVehicle) {
107 // If there is only one battery then it is the lowest
108 if (_activeVehicle.batteries.count === 1) {
109 _lowestBatteryId = _activeVehicle.batteries.get(0).id.rawValue
110 return
111 }
112
113 // If we have valid chargeState for all batteries we use that to determine lowest battery
114 let allHaveChargeState = true
115 for (var i = 0; i < _activeVehicle.batteries.count; i++) {
116 let battery = _activeVehicle.batteries.get(i)
117 if (battery.chargeState.rawValue === MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNDEFINED) {
118 allHaveChargeState = false
119 break
120 }
121 }
122 if (allHaveChargeState) {
123 let lowestBattery = _activeVehicle.batteries.get(0)
124 let lowestBatteryId = lowestBattery.id.rawValue
125 for (var i = 1; i < _activeVehicle.batteries.count; i++) {
126 let battery = _activeVehicle.batteries.get(i)
127 if (battery.chargeState.rawValue > lowestBattery.chargeState.rawValue) {
128 lowestBattery = battery
129 lowestBatteryId = battery.id.rawValue
130 }
131 }
132 _lowestBatteryId = lowestBatteryId
133 return
134 }
135 }
136
137 // Couldn't determine lowest battery, show all
138 _lowestBatteryId = -1
139 }
140
141 function _recalcLowestBatteryId() {
142 if (!_activeVehicle || _activeVehicle.batteries.count === 0) {
143 _lowestBatteryId = -1
144 return
145 }
146 if (_batterySettings.valueDisplay.rawValue === 0) {
147 // User wants percentage display so use that if available
148 _recalcLowestBatteryIdFromPercentage()
149 } else if (_batterySettings.valueDisplay.rawValue === 1) {
150 // User wants voltage display so use that if available
151 _recalcLowestBatteryIdFromVoltage()
152 }
153 // If we still dont have a lowest battery id then try charge state
154 if (_lowestBatteryId === -1) {
155 _recalcLowestBatteryIdFromChargeState()
156 }
157 }
158
159 Component.onCompleted: _recalcLowestBatteryId()
160
161 Connections {
162 target: _activeVehicle ? _activeVehicle.batteries : null
163 function onCountChanged() {_recalcLowestBatteryId() }
164 }
165
166 QGCPalette { id: qgcPal }
167
168 RowLayout {
169 id: batteryIndicatorRow
170 anchors.top: parent.top
171 anchors.bottom: parent.bottom
172 spacing: ScreenTools.defaultFontPixelWidth / 2
173
174 Repeater {
175 model: _activeVehicle ? _activeVehicle.batteries : 0
176
177 Loader {
178 Layout.fillHeight: true
179 sourceComponent: batteryVisual
180 visible: control._lowestBatteryId === -1 || object.id.rawValue === control._lowestBatteryId || !control._batterySettings.consolidateMultipleBatteries.rawValue
181
182 property var battery: object
183 }
184 }
185 }
186
187 MouseArea {
188 anchors.fill: parent
189 onClicked: mainWindow.showIndicatorDrawer(batteryPopup, control)
190 }
191
192 Component {
193 id: batteryPopup
194
195 ToolIndicatorPage {
196 showExpand: expandedComponent ? true : false
197 waitForParameters: false
198 expandedComponentWaitForParameters: true
199 contentComponent: batteryContentComponent
200 expandedComponent: batteryExpandedComponent
201 }
202 }
203
204 Component {
205 id: batteryVisual
206
207 Row {
208 Layout.fillHeight: true
209 spacing: ScreenTools.defaultFontPixelWidth / 4
210
211 function getBatteryColor() {
212 switch (battery.chargeState.rawValue) {
213 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_OK:
214 if (!isNaN(battery.percentRemaining.rawValue)) {
215 if (battery.percentRemaining.rawValue > threshold1) {
216 return qgcPal.colorGreen
217 } else if (battery.percentRemaining.rawValue > threshold2) {
218 return qgcPal.colorYellowGreen
219 } else {
220 return qgcPal.colorYellow
221 }
222 } else {
223 return qgcPal.text
224 }
225 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_LOW:
226 return qgcPal.colorOrange
227 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_CRITICAL:
228 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_EMERGENCY:
229 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_FAILED:
230 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNHEALTHY:
231 return qgcPal.colorRed
232 default:
233 return qgcPal.text
234 }
235 }
236
237 function getBatterySvgSource() {
238 switch (battery.chargeState.rawValue) {
239 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_OK:
240 if (!isNaN(battery.percentRemaining.rawValue)) {
241 if (battery.percentRemaining.rawValue > threshold1) {
242 return "/qmlimages/BatteryGreen.svg"
243 } else if (battery.percentRemaining.rawValue > threshold2) {
244 return "/qmlimages/BatteryYellowGreen.svg"
245 } else {
246 return "/qmlimages/BatteryYellow.svg"
247 }
248 }
249 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_LOW:
250 return "/qmlimages/BatteryOrange.svg" // Low with orange svg
251 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_CRITICAL:
252 return "/qmlimages/BatteryCritical.svg" // Critical with red svg
253 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_EMERGENCY:
254 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_FAILED:
255 case MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNHEALTHY:
256 return "/qmlimages/BatteryEMERGENCY.svg" // Exclamation mark
257 default:
258 return "/qmlimages/Battery.svg" // Fallback if percentage is unavailable
259 }
260 }
261
262 function getBatteryPercentageText() {
263 if (!isNaN(battery.percentRemaining.rawValue)) {
264 if (battery.percentRemaining.rawValue > 98.9) {
265 return qsTr("100%")
266 } else {
267 return battery.percentRemaining.valueString + battery.percentRemaining.units
268 }
269 } else if (!isNaN(battery.voltage.rawValue)) {
270 return battery.voltage.valueString + battery.voltage.units
271 } else if (battery.chargeState.rawValue !== MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNDEFINED) {
272 return battery.chargeState.enumStringValue
273 }
274 return qsTr("n/a")
275 }
276
277 function getBatteryVoltageText() {
278 if (!isNaN(battery.voltage.rawValue)) {
279 return battery.voltage.valueString + battery.voltage.units
280 } else if (battery.chargeState.rawValue !== MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNDEFINED) {
281 return battery.chargeState.enumStringValue
282 }
283 return qsTr("n/a")
284 }
285
286 Timer {
287 id: debounceRecalcTimer
288 interval: 50
289 running: false
290 repeat: false
291 onTriggered: {
292 control._recalcLowestBatteryId()
293 }
294 }
295 Connections {
296 target: battery.percentRemaining
297 function onRawValueChanged() {
298 debounceRecalcTimer.restart()
299 }
300 }
301 Connections {
302 target: battery.voltage
303 function onRawValueChanged() {
304 debounceRecalcTimer.restart()
305 }
306 }
307 Connections {
308 target: battery.chargeState
309 function onRawValueChanged() {
310 debounceRecalcTimer.restart()
311 }
312 }
313
314 QGCColoredImage {
315 anchors.top: parent.top
316 anchors.bottom: parent.bottom
317 width: height
318 sourceSize.width: width
319 source: getBatterySvgSource()
320 fillMode: Image.PreserveAspectFit
321 color: getBatteryColor()
322 }
323
324 ColumnLayout {
325 id: batteryInfoColumn
326 anchors.top: parent.top
327 anchors.bottom: parent.bottom
328 spacing: 0
329
330 QGCLabel {
331 Layout.alignment: Qt.AlignHCenter
332 verticalAlignment: Text.AlignVCenter
333 color: qgcPal.text
334 text: getBatteryPercentageText()
335 font.pointSize: _showBoth ? ScreenTools.defaultFontPointSize : ScreenTools.mediumFontPointSize
336 visible: _showBoth || _showPercentage
337 }
338
339 QGCLabel {
340 Layout.alignment: Qt.AlignHCenter
341 font.pointSize: _showBoth ? ScreenTools.defaultFontPointSize : ScreenTools.mediumFontPointSize
342 color: qgcPal.text
343 text: getBatteryVoltageText()
344 visible: _showBoth || _showVoltage
345 }
346 }
347 }
348 }
349
350 Component {
351 id: batteryContentComponent
352
353 ColumnLayout {
354 spacing: ScreenTools.defaultFontPixelHeight / 2
355
356 Component {
357 id: batteryValuesAvailableComponent
358
359 QtObject {
360 property bool functionAvailable: battery.function.rawValue !== MAVLinkEnums.MAV_BATTERY_FUNCTION_UNKNOWN
361 property bool showFunction: functionAvailable && battery.function.rawValue != MAVLinkEnums.MAV_BATTERY_FUNCTION_ALL
362 property bool temperatureAvailable: !isNaN(battery.temperature.rawValue)
363 property bool currentAvailable: !isNaN(battery.current.rawValue)
364 property bool mahConsumedAvailable: !isNaN(battery.mahConsumed.rawValue)
365 property bool timeRemainingAvailable: !isNaN(battery.timeRemaining.rawValue)
366 property bool percentRemainingAvailable: !isNaN(battery.percentRemaining.rawValue)
367 property bool chargeStateAvailable: battery.chargeState.rawValue !== MAVLinkEnums.MAV_BATTERY_CHARGE_STATE_UNDEFINED
368 }
369 }
370
371 Repeater {
372 model: _activeVehicle ? _activeVehicle.batteries : 0
373
374 SettingsGroupLayout {
375 heading: qsTr("Battery %1").arg(_activeVehicle.batteries.length === 1 ? qsTr("Status") : object.id.rawValue)
376 contentSpacing: 0
377 showDividers: false
378
379 property var batteryValuesAvailable: batteryValuesAvailableLoader.item
380
381 Loader {
382 id: batteryValuesAvailableLoader
383 sourceComponent: batteryValuesAvailableComponent
384
385 property var battery: object
386 }
387
388 LabelledLabel {
389 label: qsTr("Charge State")
390 labelText: object.chargeState.enumStringValue
391 visible: batteryValuesAvailable.chargeStateAvailable
392 }
393
394 LabelledLabel {
395 label: qsTr("Remaining")
396 labelText: object.timeRemainingStr.value
397 visible: batteryValuesAvailable.timeRemainingAvailable
398 }
399
400 LabelledLabel {
401 label: qsTr("Remaining")
402 labelText: object.percentRemaining.valueString + " " + object.percentRemaining.units
403 visible: batteryValuesAvailable.percentRemainingAvailable
404 }
405
406 LabelledLabel {
407 label: qsTr("Voltage")
408 labelText: object.voltage.valueString + " " + object.voltage.units
409 }
410
411 LabelledLabel {
412 label: qsTr("Consumed")
413 labelText: object.mahConsumed.valueString + " " + object.mahConsumed.units
414 visible: batteryValuesAvailable.mahConsumedAvailable
415 }
416
417 LabelledLabel {
418 label: qsTr("Temperature")
419 labelText: object.temperature.valueString + " " + object.temperature.units
420 visible: batteryValuesAvailable.temperatureAvailable
421 }
422
423 LabelledLabel {
424 label: qsTr("Function")
425 labelText: object.function.enumStringValue
426 visible: batteryValuesAvailable.showFunction
427 }
428 }
429 }
430 }
431 }
432
433 Component {
434 id: batteryExpandedComponent
435
436 ColumnLayout {
437 spacing: ScreenTools.defaultFontPixelHeight / 2
438
439 property real batteryIconHeight: ScreenTools.defaultFontPixelWidth * 3
440
441 FactPanelController { id: controller }
442
443 SettingsGroupLayout {
444 heading: qsTr("Battery Display")
445 Layout.fillWidth: true
446
447 FactCheckBoxSlider {
448 Layout.fillWidth: true
449 fact: _batterySettings.consolidateMultipleBatteries
450 text: qsTr("Only show battery with lowest charge")
451 visible: fact.userVisible
452 }
453
454 LabelledFactComboBox {
455 label: qsTr("Value")
456 fact: _batterySettings.valueDisplay
457 visible: fact.userVisible
458 }
459
460 ColumnLayout {
461 QGCLabel { text: qsTr("Coloring") }
462
463 RowLayout {
464 spacing: ScreenTools.defaultFontPixelWidth
465
466 // Battery 100%
467 RowLayout {
468 spacing: ScreenTools.defaultFontPixelWidth * 0.05 // Tighter spacing for icon and label
469 QGCColoredImage {
470 source: "/qmlimages/BatteryGreen.svg"
471 width: height
472 height: batteryIconHeight
473 fillMode: Image.PreserveAspectFit
474 color: qgcPal.colorGreen
475 }
476 QGCLabel { text: qsTr("100%") }
477 }
478
479 // Threshold 1
480 RowLayout {
481 spacing: ScreenTools.defaultFontPixelWidth * 0.05 // Tighter spacing for icon and field
482 QGCColoredImage {
483 source: "/qmlimages/BatteryYellowGreen.svg"
484 width: height
485 height: batteryIconHeight
486 fillMode: Image.PreserveAspectFit
487 color: qgcPal.colorYellowGreen
488 }
489 FactTextField {
490 id: threshold1Field
491 fact: _batterySettings.threshold1
492 implicitWidth: ScreenTools.defaultFontPixelWidth * 6
493 height: ScreenTools.defaultFontPixelHeight * 1.5
494 enabled: fact.userVisible
495 onEditingFinished: {
496 // Validate and set the new threshold value
497 _batterySettings.setThreshold1(parseInt(text));
498 }
499 }
500 }
501
502 // Threshold 2
503 RowLayout {
504 spacing: ScreenTools.defaultFontPixelWidth * 0.05 // Tighter spacing for icon and field
505 QGCColoredImage {
506 source: "/qmlimages/BatteryYellow.svg"
507 width: height
508 height: batteryIconHeight
509 fillMode: Image.PreserveAspectFit
510 color: qgcPal.colorYellow
511 }
512 FactTextField {
513 fact: _batterySettings.threshold2
514 implicitWidth: ScreenTools.defaultFontPixelWidth * 6
515 height: ScreenTools.defaultFontPixelHeight * 1.5
516 enabled: fact.userVisible
517 onEditingFinished: {
518 // Validate and set the new threshold value
519 _batterySettings.setThreshold2(parseInt(text));
520 }
521 }
522 }
523
524 // Low state
525 RowLayout {
526 spacing: ScreenTools.defaultFontPixelWidth * 0.05 // Tighter spacing for icon and label
527 QGCColoredImage {
528 source: "/qmlimages/BatteryOrange.svg"
529 width: height
530 height: batteryIconHeight
531 fillMode: Image.PreserveAspectFit
532 color: qgcPal.colorOrange
533 }
534 QGCLabel { text: qsTr("Low") }
535 }
536
537 // Critical state
538 RowLayout {
539 spacing: ScreenTools.defaultFontPixelWidth * 0.05 // Tighter spacing for icon and label
540 QGCColoredImage {
541 source: "/qmlimages/BatteryCritical.svg"
542 width: height
543 height: batteryIconHeight
544 fillMode: Image.PreserveAspectFit
545 color: qgcPal.colorRed
546 }
547 QGCLabel { text: qsTr("Critical") }
548 }
549 }
550 }
551 }
552
553 Loader {
554 Layout.fillWidth: true
555 source: _activeVehicle.expandedToolbarIndicatorSource("Battery")
556 }
557
558 SettingsGroupLayout {
559 visible: _activeVehicle.autopilotPlugin.knownVehicleComponentAvailable(AutoPilotPlugin.KnownPowerVehicleComponent) &&
560 QGroundControl.corePlugin.showAdvancedUI
561
562 LabelledButton {
563 label: qsTr("Vehicle Power")
564 buttonText: qsTr("Configure")
565
566 onClicked: {
567 mainWindow.showKnownVehicleComponentConfigPage(AutoPilotPlugin.KnownPowerVehicleComponent)
568 mainWindow.closeIndicatorDrawer()
569 }
570 }
571 }
572 }
573 }
574}