Mercurial > ~darius > hgwebdir.cgi > OverviewJD
comparison OverviewMobile.qml @ 0:57ffb39f29d4
First commit of new carousel page to allow battery charging current to
be adjusted.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Mon, 13 Dec 2021 23:05:38 +1030 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:57ffb39f29d4 |
---|---|
1 import QtQuick 1.1 | |
2 import com.victron.velib 1.0 | |
3 import "utils.js" as Utils | |
4 | |
5 OverviewPage { | |
6 id: root | |
7 | |
8 property variant sys: theSystem | |
9 property string settingsBindPreffix: "com.victronenergy.settings" | |
10 property string pumpBindPreffix: "com.victronenergy.pump.startstop0" | |
11 property variant activeNotifications: NotificationCenter.notifications.filter( | |
12 function isActive(obj) { return obj.active} ) | |
13 property string noAdjustableByDmc: qsTr("This setting is disabled when a Digital Multi Control " + | |
14 "is connected. If it was recently disconnected execute " + | |
15 "\"Redetect system\" that is avalible on the inverter menu page.") | |
16 property string noAdjustableByBms: qsTr("This setting is disabled when a VE.Bus BMS " + | |
17 "is connected. If it was recently disconnected execute " + | |
18 "\"Redetect system\" that is avalible on the inverter menu page.") | |
19 property string noAdjustableTextByConfig: qsTr("This setting is disabled. " + | |
20 "Possible reasons are \"Overruled by remote\" is not enabled or " + | |
21 "an assistant is preventing the adjustment. Please, check " + | |
22 "the inverter configuration with VEConfigure.") | |
23 property int numberOfMultis: 0 | |
24 property string vebusPrefix: "" | |
25 | |
26 // Keeps track of which button on the bottom row is active | |
27 property int buttonIndex: 0 | |
28 | |
29 title: qsTr("Mobile") | |
30 | |
31 Component.onCompleted: discoverMulti() | |
32 | |
33 ListView { | |
34 id: pwColumn | |
35 | |
36 property int tilesCount: solarTile.visible || dcSystem.visible ? 3 : 2 | |
37 property int tileHeight: Math.ceil(height / tilesCount) | |
38 interactive: false // static tiles | |
39 | |
40 width: 136 | |
41 anchors { | |
42 left: parent.left | |
43 top: parent.top; | |
44 bottom: acModeButton.top; | |
45 } | |
46 | |
47 model: VisualItemModel { | |
48 Tile { | |
49 width: pwColumn.width | |
50 height: visible ? pwColumn.tileHeight : 0 | |
51 title: qsTr("AC INPUT") | |
52 color: "#82acde" | |
53 visible: !dcSystem.visible || !solarTile.visible | |
54 values: [ | |
55 TileText { | |
56 text: sys.acInput.power.uiText | |
57 font.pixelSize: 30 | |
58 } | |
59 | |
60 ] | |
61 } | |
62 | |
63 TileAcPower { | |
64 width: pwColumn.width | |
65 height: visible ? pwColumn.tileHeight : 0 | |
66 title: qsTr("AC LOADS") | |
67 color: "#e68e8a" | |
68 values: [ | |
69 TileText { | |
70 text: sys.acLoad.power.uiText | |
71 font.pixelSize: 30 | |
72 } | |
73 ] | |
74 } | |
75 | |
76 Tile { | |
77 id: solarTile | |
78 width: pwColumn.width | |
79 height: visible ? pwColumn.tileHeight : 0 | |
80 title: qsTr("PV CHARGER") | |
81 color: "#2cc36b" | |
82 visible : sys.pvCharger.power.valid | |
83 | |
84 values: [ | |
85 TileText { | |
86 font.pixelSize: 30 | |
87 text: sys.pvCharger.power.uiText | |
88 } | |
89 ] | |
90 } | |
91 Tile { | |
92 id: dcSystem | |
93 width: pwColumn.width | |
94 height: visible ? pwColumn.tileHeight : 0 | |
95 title: qsTr("DC SYSTEM") | |
96 color: "#16a085" | |
97 visible : hasDcSys.value === 1 | |
98 | |
99 VBusItem { | |
100 id: hasDcSys | |
101 bind: Utils.path(settingsBindPreffix, "/Settings/SystemSetup/HasDcSystem") | |
102 } | |
103 | |
104 values: [ | |
105 TileText { | |
106 font.pixelSize: 30 | |
107 text: sys.dcSystem.power.format(0) | |
108 }, | |
109 TileText { | |
110 text: !sys.dcSystem.power.valid ? "---" : | |
111 sys.dcSystem.power.value < 0 ? qsTr("to battery") : qsTr("from battery") | |
112 } | |
113 ] | |
114 } | |
115 } | |
116 } | |
117 | |
118 Tile { | |
119 id: logoTile | |
120 | |
121 color: "#575748" | |
122 height: 120 | |
123 anchors { | |
124 left: pwColumn.right | |
125 right: tanksColum.left | |
126 top: parent.top | |
127 } | |
128 | |
129 MbIcon { | |
130 x: 1 | |
131 y: 1 | |
132 // see below, so the svg instead of a png if there is a 1x1 image | |
133 visible: customImage.sourceSize.width === 1 && customImage.sourceSize.height === 1 | |
134 iconId: "mobile-builder-logo-svg" | |
135 } | |
136 | |
137 // The uploaded png, the default is a 1x1 transparent pixel now. | |
138 Image { | |
139 id: customImage | |
140 source: "image://theme/mobile-builder-logo" | |
141 anchors.centerIn: parent | |
142 } | |
143 } | |
144 | |
145 Tile { | |
146 id: batteryTile | |
147 height: 112 | |
148 title: qsTr("BATTERY") | |
149 anchors { | |
150 left: pwColumn.right | |
151 right: stateTile.left | |
152 top: logoTile.bottom | |
153 bottom: acModeButton.top | |
154 } | |
155 | |
156 values: [ | |
157 TileText { | |
158 text: sys.battery.soc.absFormat(0) | |
159 font.pixelSize: 30 | |
160 height: 32 | |
161 }, | |
162 TileText { | |
163 text: { | |
164 if (!sys.battery.state.valid) | |
165 return "---" | |
166 switch(sys.battery.state.value) { | |
167 case sys.batteryStateIdle: return qsTr("idle") | |
168 case sys.batteryStateCharging : return qsTr("charging") | |
169 case sys.batteryStateDischarging : return qsTr("discharging") | |
170 } | |
171 } | |
172 }, | |
173 TileText { | |
174 text: sys.battery.power.absFormat(0) | |
175 }, | |
176 TileText { | |
177 text: sys.battery.voltage.format(1) + " " + sys.battery.current.format(1) | |
178 } | |
179 ] | |
180 } | |
181 | |
182 Tile { | |
183 id: stateTile | |
184 | |
185 width: 104 | |
186 title: qsTr("STATUS") | |
187 color: "#4789d0" | |
188 | |
189 anchors { | |
190 right: tanksColum.left | |
191 top: logoTile.bottom | |
192 bottom: acModeButton.top | |
193 } | |
194 | |
195 Timer { | |
196 id: wallClock | |
197 | |
198 running: true | |
199 repeat: true | |
200 interval: 1000 | |
201 triggeredOnStart: true | |
202 onTriggered: time = Qt.formatDateTime(new Date(), "hh:mm") | |
203 | |
204 property string time | |
205 } | |
206 | |
207 values: [ | |
208 TileText { | |
209 id: systemTile | |
210 text: wallClock.time | |
211 font.pixelSize: 30 | |
212 }, | |
213 TileText { | |
214 property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } | |
215 property VeQuickItem speed: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Speed") } | |
216 property VeQuickItem speedUnit: VeQuickItem { uid: "dbus/com.victronenergy.settings/Settings/Gps/SpeedUnit" } | |
217 | |
218 text: speed.value === undefined ? "" : getValue() | |
219 visible: speed.value !== undefined && speedUnit.value !== undefined | |
220 | |
221 function getValue() | |
222 { | |
223 if (speedUnit.value === "km/h") | |
224 return (speed.value * 3.6).toFixed(1) + speedUnit.value | |
225 if (speedUnit.value === "mph") | |
226 return (speed.value * 2.236936).toFixed(1) + speedUnit.value | |
227 if (speedUnit.value === "kt") | |
228 return (speed.value * (3600/1852)).toFixed(1) + speedUnit.value | |
229 return speed.value.toFixed(2) + "m/s" | |
230 } | |
231 }, | |
232 Marquee { | |
233 text: notificationText() | |
234 width: stateTile.width | |
235 interval: 100 | |
236 fontSize: 13 | |
237 } | |
238 ] | |
239 } | |
240 | |
241 ListView { | |
242 id: tanksColum | |
243 | |
244 property int tileHeight: Math.ceil(height / Math.max(count, 2)) | |
245 width: 134 | |
246 interactive: false // static tiles | |
247 model: TankModel { id: tankModel } | |
248 delegate: TileTank { | |
249 // Without an intermediate assignment this will trigger a binding loop warning. | |
250 property variant theService: DBusServices.get(buddy.id) | |
251 service: theService | |
252 width: tanksColum.width | |
253 height: tanksColum.tileHeight | |
254 pumpBindPrefix: root.pumpBindPreffix | |
255 compact: tankModel.rowCount > (pumpButton.pumpEnabled ? 4 : 5) | |
256 Connections { | |
257 target: scrollTimer | |
258 onTriggered: doScroll() | |
259 } | |
260 } | |
261 | |
262 anchors { | |
263 top: root.top | |
264 bottom: pumpButton.pumpEnabled ? acModeButton.top : acModeButton.bottom | |
265 right: root.right | |
266 } | |
267 | |
268 // Synchronise tank name text scroll start | |
269 Timer { | |
270 id: scrollTimer | |
271 interval: 15000 | |
272 repeat: true | |
273 running: root.active && tankModel.rowCount > 4 | |
274 } | |
275 | |
276 Tile { | |
277 title: qsTr("TANKS") | |
278 anchors.fill: parent | |
279 values: TileText { | |
280 text: qsTr("No tanks found") | |
281 width: parent.width | |
282 wrapMode: Text.WordWrap | |
283 } | |
284 z: -1 | |
285 } | |
286 } | |
287 | |
288 Keys.forwardTo: [keyHandler] | |
289 | |
290 Item { | |
291 id: keyHandler | |
292 Keys.onLeftPressed: { | |
293 if (buttonIndex > 0) | |
294 buttonIndex-- | |
295 | |
296 event.accepted = true | |
297 } | |
298 | |
299 Keys.onRightPressed: { | |
300 if (buttonIndex < (pumpButton.pumpEnabled ? 2 : 1)) | |
301 buttonIndex++ | |
302 | |
303 event.accepted = true | |
304 } | |
305 } | |
306 | |
307 MouseArea { | |
308 anchors.fill: parent | |
309 enabled: parent.active | |
310 onPressed: mouse.accepted = acCurrentButton.expanded | |
311 onClicked: acCurrentButton.cancel() | |
312 } | |
313 | |
314 TileSpinBox { | |
315 id: acCurrentButton | |
316 | |
317 anchors.bottom: parent.bottom | |
318 anchors.left: parent.left | |
319 isCurrentItem: (buttonIndex == 0) | |
320 focus: root.active && isCurrentItem | |
321 | |
322 bind: Utils.path(vebusPrefix, "/Ac/ActiveIn/CurrentLimit") | |
323 title: qsTr("AC CURRENT LIMIT") | |
324 color: containsMouse && !editMode ? "#d3d3d3" : "#A8A8A8" | |
325 width: pumpButton.pumpEnabled ? 160 : 173 | |
326 fontPixelSize: 14 | |
327 unit: "A" | |
328 readOnly: currentLimitIsAdjustable.value !== 1 || numberOfMultis > 1 | |
329 buttonColor: "#979797" | |
330 | |
331 VBusItem { id: currentLimitIsAdjustable; bind: Utils.path(vebusPrefix, "/Ac/ActiveIn/CurrentLimitIsAdjustable") } | |
332 | |
333 Keys.onSpacePressed: showErrorToast(event) | |
334 | |
335 function editIsAllowed() { | |
336 if (numberOfMultis > 1) { | |
337 toast.createToast(qsTr("It is not possible to change this setting when there are more than one inverter connected."), 5000) | |
338 return false | |
339 } | |
340 | |
341 if (currentLimitIsAdjustable.value === 0) { | |
342 if (dmc.valid) { | |
343 toast.createToast(noAdjustableByDmc, 5000) | |
344 return false | |
345 } | |
346 if (bms.valid) { | |
347 toast.createToast(noAdjustableByBms, 5000) | |
348 return false | |
349 } | |
350 if (!dmc.valid && !bms.valid) { | |
351 toast.createToast(noAdjustableTextByConfig, 5000) | |
352 return false | |
353 } | |
354 } | |
355 | |
356 return true | |
357 } | |
358 | |
359 function showErrorToast(event) { | |
360 editIsAllowed() | |
361 event.accepted = true | |
362 } | |
363 } | |
364 | |
365 Tile { | |
366 id: acModeButton | |
367 anchors.left: acCurrentButton.right | |
368 anchors.bottom: parent.bottom | |
369 property variant texts: { 4: qsTr("OFF"), 3: qsTr("ON"), 1: qsTr("CHARGER ONLY") } | |
370 property int value: mode.valid ? mode.value : 3 | |
371 property int shownValue: applyAnimation2.running ? applyAnimation2.pendingValue : value | |
372 | |
373 isCurrentItem: (buttonIndex == 1) | |
374 focus: root.active && isCurrentItem | |
375 | |
376 editable: true | |
377 readOnly: !modeIsAdjustable.valid || modeIsAdjustable.value !== 1 || numberOfMultis > 1 | |
378 width: pumpButton.pumpEnabled ? 160 : 173 | |
379 height: 45 | |
380 color: acModeButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8" | |
381 title: qsTr("AC MODE") | |
382 | |
383 values: [ | |
384 TileText { | |
385 text: modeIsAdjustable.valid && numberOfMultis === 1 ? qsTr("%1").arg(acModeButton.texts[acModeButton.shownValue]) : qsTr("NOT AVAILABLE") | |
386 } | |
387 ] | |
388 | |
389 VBusItem { id: mode; bind: Utils.path(vebusPrefix, "/Mode") } | |
390 VBusItem { id: modeIsAdjustable; bind: Utils.path(vebusPrefix,"/ModeIsAdjustable") } | |
391 | |
392 Keys.onSpacePressed: edit() | |
393 | |
394 function edit() { | |
395 if (!mode.valid) | |
396 return | |
397 | |
398 if (numberOfMultis > 1) { | |
399 toast.createToast(qsTr("It is not possible to change this setting when there are more than one inverter connected."), 5000) | |
400 return | |
401 } | |
402 | |
403 if (modeIsAdjustable.value === 0) { | |
404 if (dmc.valid) | |
405 toast.createToast(noAdjustableByDmc, 5000) | |
406 if (bms.valid) | |
407 toast.createToast(noAdjustableByBms, 5000) | |
408 if (!dmc.valid && !bms.valid) | |
409 toast.createToast(noAdjustableTextByConfig, 5000) | |
410 return | |
411 } | |
412 | |
413 switch (shownValue) { | |
414 case 4: | |
415 applyAnimation2.pendingValue = 3 | |
416 break; | |
417 case 3: | |
418 applyAnimation2.pendingValue = 1 | |
419 break; | |
420 case 1: | |
421 applyAnimation2.pendingValue = 4 | |
422 break; | |
423 } | |
424 | |
425 applyAnimation2.restart() | |
426 } | |
427 | |
428 MouseArea { | |
429 id: acModeButtonMouseArea | |
430 anchors.fill: parent | |
431 property bool containsPressed: containsMouse && pressed | |
432 onClicked: { | |
433 buttonIndex = 1 | |
434 parent.edit() | |
435 } | |
436 } | |
437 | |
438 Rectangle { | |
439 id: timerRect2 | |
440 height: 2 | |
441 width: acModeButton.width * 0.8 | |
442 visible: applyAnimation2.running | |
443 anchors { | |
444 bottom: parent.bottom; bottomMargin: 5 | |
445 horizontalCenter: parent.horizontalCenter | |
446 } | |
447 } | |
448 | |
449 SequentialAnimation { | |
450 id: applyAnimation2 | |
451 | |
452 property int pendingValue | |
453 | |
454 NumberAnimation { | |
455 target: timerRect2 | |
456 property: "width" | |
457 from: 0 | |
458 to: acModeButton.width * 0.8 | |
459 duration: 3000 | |
460 } | |
461 | |
462 ColorAnimation { | |
463 target: acModeButton | |
464 property: "color" | |
465 from: "#A8A8A8" | |
466 to: "#4789d0" | |
467 duration: 200 | |
468 } | |
469 | |
470 ColorAnimation { | |
471 target: acModeButton | |
472 property: "color" | |
473 from: "#4789d0" | |
474 to: "#A8A8A8" | |
475 duration: 200 | |
476 } | |
477 PropertyAction { | |
478 target: timerRect2 | |
479 property: "width" | |
480 value: 0 | |
481 } | |
482 | |
483 ScriptAction { script: mode.setValue(applyAnimation2.pendingValue) } | |
484 | |
485 PauseAnimation { duration: 1000 } | |
486 } | |
487 } | |
488 | |
489 Tile { | |
490 id: pumpButton | |
491 | |
492 anchors.left: acModeButton.right | |
493 anchors.bottom: parent.bottom | |
494 | |
495 property variant texts: [ qsTr("AUTO"), qsTr("ON"), qsTr("OFF")] | |
496 property int value: 0 | |
497 property bool reset: false | |
498 property bool pumpEnabled: pumpRelay.value === 3 | |
499 | |
500 show: pumpEnabled | |
501 isCurrentItem: (buttonIndex == 2) | |
502 focus: root.active && isCurrentItem | |
503 | |
504 title: qsTr("PUMP") | |
505 width: show ? 160 : 0 | |
506 height: 45 | |
507 editable: true | |
508 readOnly: false | |
509 color: pumpButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8" | |
510 | |
511 VBusItem { id: pump; bind: Utils.path(settingsBindPreffix, "/Settings/Pump0/Mode") } | |
512 VBusItem { id: pumpRelay; bind: Utils.path(settingsBindPreffix, "/Settings/Relay/Function") } | |
513 | |
514 values: [ | |
515 TileText { | |
516 text: pumpButton.pumpEnabled ? qsTr("%1").arg(pumpButton.texts[pumpButton.value]) : qsTr("DISABLED") | |
517 } | |
518 ] | |
519 | |
520 Keys.onSpacePressed: edit() | |
521 | |
522 function edit() { | |
523 if (!pumpEnabled) { | |
524 toast.createToast(qsTr("Pump functionality is not enabled. To enable it go to the relay settings page and set function to \"Tank pump\""), 5000) | |
525 return | |
526 } | |
527 | |
528 reset = true | |
529 applyAnimation.restart() | |
530 reset = false | |
531 | |
532 if (value < 2) | |
533 value++ | |
534 else | |
535 value = 0 | |
536 } | |
537 | |
538 MouseArea { | |
539 id: pumpButtonMouseArea | |
540 property bool containsPressed: containsMouse && pressed | |
541 anchors.fill: parent | |
542 onClicked: { | |
543 buttonIndex = 2 | |
544 parent.edit() | |
545 } | |
546 } | |
547 | |
548 Rectangle { | |
549 id: timerRect | |
550 height: 2 | |
551 width: pumpButton.width * 0.8 | |
552 visible: applyAnimation.running | |
553 anchors { | |
554 bottom: parent.bottom; bottomMargin: 5 | |
555 horizontalCenter: parent.horizontalCenter | |
556 } | |
557 } | |
558 | |
559 SequentialAnimation { | |
560 id: applyAnimation | |
561 alwaysRunToEnd: false | |
562 NumberAnimation { | |
563 target: timerRect | |
564 property: "width" | |
565 from: 0 | |
566 to: pumpButton.width * 0.8 | |
567 duration: 3000 | |
568 } | |
569 | |
570 ColorAnimation { | |
571 target: pumpButton | |
572 property: "color" | |
573 from: "#A8A8A8" | |
574 to: "#4789d0" | |
575 duration: 200 | |
576 } | |
577 | |
578 ColorAnimation { | |
579 target: pumpButton | |
580 property: "color" | |
581 from: "#4789d0" | |
582 to: "#A8A8A8" | |
583 duration: 200 | |
584 } | |
585 PropertyAction { | |
586 target: timerRect | |
587 property: "width" | |
588 value: 0 | |
589 } | |
590 // Do not set value if the animation is restarted by user pressing the button | |
591 // to move between options | |
592 onCompleted: if (!pumpButton.reset) pump.setValue(pumpButton.value) | |
593 } | |
594 } | |
595 | |
596 // When new service is found check if is a tank sensor | |
597 Connections { | |
598 target: DBusServices | |
599 onDbusServiceFound: addService(service) | |
600 } | |
601 | |
602 function addService(service) | |
603 { | |
604 if (service.type === DBusService.DBUS_SERVICE_MULTI) { | |
605 numberOfMultis++ | |
606 if (vebusPrefix === "") | |
607 vebusPrefix = service.name; | |
608 } | |
609 } | |
610 | |
611 // Check available services to find tank sesnsors | |
612 function discoverMulti() | |
613 { | |
614 for (var i = 0; i < DBusServices.count; i++) { | |
615 if (DBusServices.at(i).type === DBusService.DBUS_SERVICE_MULTI) { | |
616 addService(DBusServices.at(i)) | |
617 } | |
618 } | |
619 } | |
620 | |
621 function notificationText() | |
622 { | |
623 if (activeNotifications.length === 0) | |
624 return qsTr("no alarms") | |
625 | |
626 var descr = [] | |
627 for (var n = 0; n < activeNotifications.length; n++) { | |
628 var notification = activeNotifications[n]; | |
629 | |
630 var text = notification.serviceName + " - " + notification.description; | |
631 if (notification.value !== "" ) | |
632 text += ": " + notification.value | |
633 | |
634 descr.push(text) | |
635 } | |
636 | |
637 return descr.join(" | ") | |
638 } | |
639 | |
640 VBusItem { id: dmc; bind: Utils.path(vebusPrefix, "/Devices/Dmc/Version") } | |
641 VBusItem { id: bms; bind: Utils.path(vebusPrefix, "/Devices/Bms/Version") } | |
642 } |