view OverviewMobile.qml @ 1:594ba407689b default tip

Show temperature instead of power now the sensor is fitted.
author Daniel O'Connor <darius@dons.net.au>
date Tue, 21 Dec 2021 15:24:31 +1030
parents 57ffb39f29d4
children
line wrap: on
line source

import QtQuick 1.1
import com.victron.velib 1.0
import "utils.js" as Utils

OverviewPage {
	id: root

	property variant sys: theSystem
	property string settingsBindPreffix: "com.victronenergy.settings"
	property string pumpBindPreffix: "com.victronenergy.pump.startstop0"
	property variant activeNotifications: NotificationCenter.notifications.filter(
											  function isActive(obj) { return obj.active} )
	property string noAdjustableByDmc: qsTr("This setting is disabled when a Digital Multi Control " +
											"is connected. If it was recently disconnected execute " +
											"\"Redetect system\" that is avalible on the inverter menu page.")
	property string noAdjustableByBms: qsTr("This setting is disabled when a VE.Bus BMS " +
											"is connected. If it was recently disconnected execute " +
											"\"Redetect system\" that is avalible on the inverter menu page.")
	property string noAdjustableTextByConfig: qsTr("This setting is disabled. " +
										   "Possible reasons are \"Overruled by remote\" is not enabled or " +
										   "an assistant is preventing the adjustment. Please, check " +
										   "the inverter configuration with VEConfigure.")
	property int numberOfMultis: 0
	property string vebusPrefix: ""

	// Keeps track of which button on the bottom row is active
	property int buttonIndex: 0

	title: qsTr("Mobile")

	Component.onCompleted: discoverMulti()

	ListView {
		id: pwColumn

		property int tilesCount: solarTile.visible || dcSystem.visible ? 3 : 2
		property int tileHeight: Math.ceil(height / tilesCount)
		interactive: false // static tiles

		width: 136
		anchors {
			left: parent.left
			top: parent.top;
			bottom: acModeButton.top;
		}

		model: VisualItemModel {
			Tile {
				width: pwColumn.width
				height: visible ? pwColumn.tileHeight : 0
				title: qsTr("AC INPUT")
				color: "#82acde"
				visible: !dcSystem.visible || !solarTile.visible
				values: [
					TileText {
						text: sys.acInput.power.uiText
						font.pixelSize: 30
					}

				]
			}

			TileAcPower {
				width: pwColumn.width
				height: visible ? pwColumn.tileHeight : 0
				title: qsTr("AC LOADS")
				color: "#e68e8a"
				values: [
					TileText {
						text: sys.acLoad.power.uiText
						font.pixelSize: 30
					}
				]
			}

			Tile {
				id: solarTile
				width: pwColumn.width
				height: visible ? pwColumn.tileHeight : 0
				title: qsTr("PV CHARGER")
				color: "#2cc36b"
				visible  : sys.pvCharger.power.valid

				values: [
					TileText {
						font.pixelSize: 30
						text: sys.pvCharger.power.uiText
					}
				]
			}
			Tile {
				id: dcSystem
				width: pwColumn.width
				height: visible ? pwColumn.tileHeight : 0
				title: qsTr("DC SYSTEM")
				color: "#16a085"
				visible  : hasDcSys.value === 1

				VBusItem {
					id: hasDcSys
					bind: Utils.path(settingsBindPreffix, "/Settings/SystemSetup/HasDcSystem")
				}

				values: [
					TileText {
						font.pixelSize: 30
						text: sys.dcSystem.power.format(0)
					},
					TileText {
						text: !sys.dcSystem.power.valid ? "---" :
							  sys.dcSystem.power.value < 0 ? qsTr("to battery") : qsTr("from battery")
					}
				]
			}
		}
	}

	Tile {
		id: logoTile

		color: "#575748"
		height: 120
		anchors {
			left: pwColumn.right
			right: tanksColum.left
			top: parent.top
		}

		MbIcon {
			x: 1
			y: 1
			// see below, so the svg instead of a png if there is a 1x1 image
			visible: customImage.sourceSize.width === 1 && customImage.sourceSize.height === 1
			iconId: "mobile-builder-logo-svg"
		}

		// The uploaded png, the default is a 1x1 transparent pixel now.
		Image {
			id: customImage
			source: "image://theme/mobile-builder-logo"
			anchors.centerIn: parent
		}
	}

	Tile {
		id: batteryTile
		height: 112
		title: qsTr("BATTERY")
		anchors {
			left: pwColumn.right
			right: stateTile.left
			top: logoTile.bottom
			bottom: acModeButton.top
		}

		values: [
			TileText {
				text: sys.battery.soc.absFormat(0)
				font.pixelSize: 30
				height: 32
			},
			TileText {
				text: {
					if (!sys.battery.state.valid)
						return "---"
					switch(sys.battery.state.value) {
						case sys.batteryStateIdle: return qsTr("idle")
						case sys.batteryStateCharging : return qsTr("charging")
						case sys.batteryStateDischarging : return qsTr("discharging")
					}
				}
			},
			TileText {
				text: sys.battery.power.absFormat(0)
			},
			TileText {
				text: sys.battery.voltage.format(1) + "   " + sys.battery.current.format(1)
			}
		]
	}

	Tile {
		id: stateTile

		width: 104
		title: qsTr("STATUS")
		color: "#4789d0"

		anchors {
			right: tanksColum.left
			top: logoTile.bottom
			bottom: acModeButton.top
		}

		Timer {
			id: wallClock

			running: true
			repeat: true
			interval: 1000
			triggeredOnStart: true
			onTriggered: time = Qt.formatDateTime(new Date(), "hh:mm")

			property string time
		}

		values: [
			TileText {
				id: systemTile
				text: wallClock.time
				font.pixelSize: 30
			},
			TileText {
				property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" }
				property VeQuickItem speed: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Speed") }
				property VeQuickItem speedUnit: VeQuickItem { uid: "dbus/com.victronenergy.settings/Settings/Gps/SpeedUnit" }

				text: speed.value === undefined ? "" : getValue()
				visible: speed.value !== undefined && speedUnit.value !== undefined

				function getValue()
				{
					if (speedUnit.value === "km/h")
						return (speed.value * 3.6).toFixed(1) + speedUnit.value
					if (speedUnit.value === "mph")
						return (speed.value * 2.236936).toFixed(1) + speedUnit.value
					if (speedUnit.value === "kt")
						return (speed.value * (3600/1852)).toFixed(1) + speedUnit.value
					return speed.value.toFixed(2) + "m/s"
				}
			},
			Marquee {
				text: notificationText()
				width: stateTile.width
				interval: 100
				fontSize: 13
			}
		]
	}

	ListView {
		id: tanksColum

		property int tileHeight: Math.ceil(height / Math.max(count, 2))
		width: 134
		interactive: false // static tiles
		model: TankModel { id: tankModel }
		delegate: TileTank {
			// Without an intermediate assignment this will trigger a binding loop warning.
			property variant theService: DBusServices.get(buddy.id)
			service: theService
			width: tanksColum.width
			height: tanksColum.tileHeight
			pumpBindPrefix: root.pumpBindPreffix
			compact: tankModel.rowCount > (pumpButton.pumpEnabled ? 4 : 5)
			Connections {
				target: scrollTimer
				onTriggered: doScroll()
			}
		}

		anchors {
			top: root.top
			bottom: pumpButton.pumpEnabled ? acModeButton.top : acModeButton.bottom
			right: root.right
		}

		// Synchronise tank name text scroll start
		Timer {
			id: scrollTimer
			interval: 15000
			repeat: true
			running: root.active && tankModel.rowCount > 4
		}

		Tile {
			title: qsTr("TANKS")
			anchors.fill: parent
			values: TileText {
				text: qsTr("No tanks found")
				width: parent.width
				wrapMode: Text.WordWrap
			}
			z: -1
		}
	}

	Keys.forwardTo: [keyHandler]

	Item {
		id: keyHandler
		Keys.onLeftPressed: {
			if (buttonIndex > 0)
				buttonIndex--

			event.accepted = true
		}

		Keys.onRightPressed: {
			if (buttonIndex < (pumpButton.pumpEnabled ? 2 : 1))
				buttonIndex++

			event.accepted = true
		}
	}

	MouseArea {
		anchors.fill: parent
		enabled: parent.active
		onPressed: mouse.accepted = acCurrentButton.expanded
		onClicked: acCurrentButton.cancel()
	}

	TileSpinBox {
		id: acCurrentButton

		anchors.bottom: parent.bottom
		anchors.left: parent.left
		isCurrentItem: (buttonIndex == 0)
		focus: root.active && isCurrentItem

		bind: Utils.path(vebusPrefix, "/Ac/ActiveIn/CurrentLimit")
		title: qsTr("AC CURRENT LIMIT")
		color: containsMouse && !editMode ? "#d3d3d3" : "#A8A8A8"
		width: pumpButton.pumpEnabled ? 160 : 173
		fontPixelSize: 14
		unit: "A"
		readOnly: currentLimitIsAdjustable.value !== 1 || numberOfMultis > 1
		buttonColor: "#979797"

		VBusItem { id: currentLimitIsAdjustable; bind: Utils.path(vebusPrefix, "/Ac/ActiveIn/CurrentLimitIsAdjustable") }

		Keys.onSpacePressed: showErrorToast(event)

		function editIsAllowed() {
			if (numberOfMultis > 1) {
				toast.createToast(qsTr("It is not possible to change this setting when there are more than one inverter connected."), 5000)
				return false
			}

			if (currentLimitIsAdjustable.value === 0) {
				if (dmc.valid) {
					toast.createToast(noAdjustableByDmc, 5000)
					return false
				}
				if (bms.valid) {
					toast.createToast(noAdjustableByBms, 5000)
					return false
				}
				if (!dmc.valid && !bms.valid) {
					toast.createToast(noAdjustableTextByConfig, 5000)
					return false
				}
			}

			return true
		}

		function showErrorToast(event) {
			editIsAllowed()
			event.accepted = true
		}
	}

	Tile {
		id: acModeButton
		anchors.left: acCurrentButton.right
		anchors.bottom: parent.bottom
		property variant texts: { 4: qsTr("OFF"), 3: qsTr("ON"), 1: qsTr("CHARGER ONLY") }
		property int value: mode.valid ? mode.value : 3
		property int shownValue: applyAnimation2.running ? applyAnimation2.pendingValue : value

		isCurrentItem: (buttonIndex == 1)
		focus: root.active && isCurrentItem

		editable: true
		readOnly: !modeIsAdjustable.valid || modeIsAdjustable.value !== 1 || numberOfMultis > 1
		width: pumpButton.pumpEnabled ? 160 : 173
		height: 45
		color: acModeButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8"
		title: qsTr("AC MODE")

		values: [
			TileText {
				text: modeIsAdjustable.valid && numberOfMultis === 1 ? qsTr("%1").arg(acModeButton.texts[acModeButton.shownValue]) : qsTr("NOT AVAILABLE")
			}
		]

		VBusItem { id: mode; bind: Utils.path(vebusPrefix, "/Mode") }
		VBusItem { id: modeIsAdjustable; bind: Utils.path(vebusPrefix,"/ModeIsAdjustable") }

		Keys.onSpacePressed: edit()

		function edit() {
			if (!mode.valid)
				return

			if (numberOfMultis > 1) {
				toast.createToast(qsTr("It is not possible to change this setting when there are more than one inverter connected."), 5000)
				return
			}

			if (modeIsAdjustable.value === 0) {
				if (dmc.valid)
					toast.createToast(noAdjustableByDmc, 5000)
				if (bms.valid)
					toast.createToast(noAdjustableByBms, 5000)
				if (!dmc.valid && !bms.valid)
					toast.createToast(noAdjustableTextByConfig, 5000)
				return
			}

			switch (shownValue) {
			case 4:
				applyAnimation2.pendingValue = 3
				break;
			case 3:
				applyAnimation2.pendingValue = 1
				break;
			case 1:
				applyAnimation2.pendingValue = 4
				break;
			}

			applyAnimation2.restart()
		}

		MouseArea {
			id: acModeButtonMouseArea
			anchors.fill: parent
			property bool containsPressed: containsMouse && pressed
			onClicked:  {
				buttonIndex = 1
				parent.edit()
			}
		}

		Rectangle {
			id: timerRect2
			height: 2
			width: acModeButton.width * 0.8
			visible: applyAnimation2.running
			anchors {
				bottom: parent.bottom; bottomMargin: 5
				horizontalCenter: parent.horizontalCenter
			}
		}

		SequentialAnimation {
			id: applyAnimation2

			property int pendingValue

			NumberAnimation {
				target: timerRect2
				property: "width"
				from: 0
				to: acModeButton.width * 0.8
				duration: 3000
			}

			ColorAnimation {
				target: acModeButton
				property: "color"
				from: "#A8A8A8"
				to: "#4789d0"
				duration: 200
			}

			ColorAnimation {
				target: acModeButton
				property: "color"
				from: "#4789d0"
				to: "#A8A8A8"
				duration: 200
			}
			PropertyAction {
				target: timerRect2
				property: "width"
				value: 0
			}

			ScriptAction { script: mode.setValue(applyAnimation2.pendingValue) }

			PauseAnimation { duration: 1000 }
		}
	}

	Tile {
		id: pumpButton

		anchors.left: acModeButton.right
		anchors.bottom: parent.bottom

		property variant texts: [ qsTr("AUTO"), qsTr("ON"), qsTr("OFF")]
		property int value: 0
		property bool reset: false
		property bool pumpEnabled: pumpRelay.value === 3

		show: pumpEnabled
		isCurrentItem: (buttonIndex == 2)
		focus: root.active && isCurrentItem

		title: qsTr("PUMP")
		width: show ? 160 : 0
		height: 45
		editable: true
		readOnly: false
		color: pumpButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8"

		VBusItem { id: pump; bind: Utils.path(settingsBindPreffix, "/Settings/Pump0/Mode") }
		VBusItem { id: pumpRelay; bind: Utils.path(settingsBindPreffix, "/Settings/Relay/Function") }

		values: [
			TileText {
				text: pumpButton.pumpEnabled ? qsTr("%1").arg(pumpButton.texts[pumpButton.value]) : qsTr("DISABLED")
			}
		]

		Keys.onSpacePressed: edit()

		function edit() {
			if (!pumpEnabled) {
				toast.createToast(qsTr("Pump functionality is not enabled. To enable it go to the relay settings page and set function to \"Tank pump\""), 5000)
				return
			}

			reset = true
			applyAnimation.restart()
			reset = false

			if (value < 2)
				value++
			else
				value = 0
		}

		MouseArea {
			id: pumpButtonMouseArea
			property bool containsPressed: containsMouse && pressed
			anchors.fill: parent
			onClicked: {
				buttonIndex = 2
				parent.edit()
			}
		}

		Rectangle {
			id: timerRect
			height: 2
			width: pumpButton.width * 0.8
			visible: applyAnimation.running
			anchors {
				bottom: parent.bottom; bottomMargin: 5
				horizontalCenter: parent.horizontalCenter
			}
		}

		SequentialAnimation {
			id: applyAnimation
			alwaysRunToEnd: false
			NumberAnimation {
				target: timerRect
				property: "width"
				from: 0
				to: pumpButton.width * 0.8
				duration: 3000
			}

			ColorAnimation {
				target: pumpButton
				property: "color"
				from: "#A8A8A8"
				to: "#4789d0"
				duration: 200
			}

			ColorAnimation {
				target: pumpButton
				property: "color"
				from: "#4789d0"
				to: "#A8A8A8"
				duration: 200
			}
			PropertyAction {
				target: timerRect
				property: "width"
				value: 0
			}
			// Do not set value if the animation is restarted by user pressing the button
			// to move between options
			onCompleted: if (!pumpButton.reset) pump.setValue(pumpButton.value)
		}
	}

	// When new service is found check if is a tank sensor
	Connections {
		target: DBusServices
		onDbusServiceFound: addService(service)
	}

	function addService(service)
	{
		if (service.type === DBusService.DBUS_SERVICE_MULTI) {
			numberOfMultis++
			if (vebusPrefix === "")
				vebusPrefix = service.name;
		}
	}

	// Check available services to find tank sesnsors
	function discoverMulti()
	{
		for (var i = 0; i < DBusServices.count; i++) {
			if (DBusServices.at(i).type === DBusService.DBUS_SERVICE_MULTI) {
				addService(DBusServices.at(i))
			}
		}
	}

	function notificationText()
	{
		if (activeNotifications.length === 0)
			return qsTr("no alarms")

		var descr = []
		for (var n = 0; n < activeNotifications.length; n++) {
			var notification = activeNotifications[n];

			var text = notification.serviceName + " - " + notification.description;
			if (notification.value !== "" )
				text += ":  " + notification.value

			descr.push(text)
		}

		return descr.join("  |  ")
	}

	VBusItem { id: dmc; bind: Utils.path(vebusPrefix, "/Devices/Dmc/Version") }
	VBusItem { id: bms; bind: Utils.path(vebusPrefix, "/Devices/Bms/Version") }
}