Skip to content

Commit

Permalink
Device List: rework to load per-service delegate files
Browse files Browse the repository at this point in the history
The _deviceDisplayInfo() in DeviceListPage is problematic as it causes
binding updates to all delegates whenever any of the displayed
delegate values change.

Instead of using this single gigantic function to configure delegates,
load per-service QML files that define their own delegate UIs. This
improvement also means that custom list items can be provided for
individual services, instead of requiring all services to use a
similar delegate UI.

All delegates should have the same appearance as before, except for
the Inverter delegate, which has been fixed to only show the power for
the currently active phase.

Issue #1338
  • Loading branch information
blammit committed Nov 26, 2024
1 parent 92212a0 commit 5eb895b
Show file tree
Hide file tree
Showing 34 changed files with 800 additions and 253 deletions.
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ set (VENUS_QML_MODULE_SOURCES
components/CurrentLimitButton.qml
components/DateSelector.qml
components/Device.qml
components/DeviceListDelegate.qml
components/DynamicValueRange.qml
components/ElectricalQuantityLabel.qml
components/EnvironmentGauge.qml
Expand Down Expand Up @@ -588,6 +589,37 @@ set (VENUS_QML_MODULE_SOURCES
pages/settings/devicelist/dc-in/PageDcMeterModel.qml
pages/settings/devicelist/dc-in/PageDcMeterAlarms.qml
pages/settings/devicelist/dc-in/PageDcMeterHistory.qml
pages/settings/devicelist/delegates/AcInDeviceListDelegate.qml
pages/settings/devicelist/delegates/DcMeterDeviceListDelegate.qml
pages/settings/devicelist/delegates/DeviceListDelegate_acload.qml
pages/settings/devicelist/delegates/DeviceListDelegate_acsystem.qml
pages/settings/devicelist/delegates/DeviceListDelegate_alternator.qml
pages/settings/devicelist/delegates/DeviceListDelegate_battery.qml
pages/settings/devicelist/delegates/DeviceListDelegate_charger.qml
pages/settings/devicelist/delegates/DeviceListDelegate_dcdc.qml
pages/settings/devicelist/delegates/DeviceListDelegate_dcgenset.qml
pages/settings/devicelist/delegates/DeviceListDelegate_dcload.qml
pages/settings/devicelist/delegates/DeviceListDelegate_dcsource.qml
pages/settings/devicelist/delegates/DeviceListDelegate_dcsystem.qml
pages/settings/devicelist/delegates/DeviceListDelegate_digitalinput.qml
pages/settings/devicelist/delegates/DeviceListDelegate_evcharger.qml
pages/settings/devicelist/delegates/DeviceListDelegate_fuelcell.qml
pages/settings/devicelist/delegates/DeviceListDelegate_genset.qml
pages/settings/devicelist/delegates/DeviceListDelegate_grid.qml
pages/settings/devicelist/delegates/DeviceListDelegate_heatpump.qml
pages/settings/devicelist/delegates/DeviceListDelegate_inverter.qml
pages/settings/devicelist/delegates/DeviceListDelegate_meteo.qml
pages/settings/devicelist/delegates/DeviceListDelegate_motordrive.qml
pages/settings/devicelist/delegates/DeviceListDelegate_multi.qml
pages/settings/devicelist/delegates/DeviceListDelegate_pulsemeter.qml
pages/settings/devicelist/delegates/DeviceListDelegate_pvinverter.qml
pages/settings/devicelist/delegates/DeviceListDelegate_solarcharger.qml
pages/settings/devicelist/delegates/DeviceListDelegate_tank.qml
pages/settings/devicelist/delegates/DeviceListDelegate_temperature.qml
pages/settings/devicelist/delegates/DeviceListDelegate_unsupported.qml
pages/settings/devicelist/delegates/DeviceListDelegate_vebus.qml
pages/settings/devicelist/delegates/DisconnectedDeviceListDelegate.qml
pages/settings/devicelist/delegates/GensetDeviceListDelegate.qml
pages/settings/devicelist/inverter/PageInverter.qml
pages/settings/devicelist/inverter/PageSolarStats.qml
pages/settings/devicelist/rs/PageMultiRs.qml
Expand Down
16 changes: 16 additions & 0 deletions components/DeviceListDelegate.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
** Copyright (C) 2023 Victron Energy B.V.
** See LICENSE.txt for license information.
*/

import QtQuick
import Victron.VenusOS

ListQuantityGroupNavigationItem {
id: root

property BaseDevice device
property BaseDeviceModel sourceModel

text: device.name
}
290 changes: 37 additions & 253 deletions pages/settings/devicelist/DeviceListPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -3,230 +3,56 @@
** See LICENSE.txt for license information.
*/

/*
* These settings are regularly brought up to date with the settings from gui-v1.
* Currently up to date with gui-v1 v5.6.6.
*/

import QtQuick
import QtQuick.Controls.impl as CP
import Victron.VenusOS

Page {
id: root

function _deviceDisplayInfo(serviceType, device, sourceModel) {
let summary = []
let url = ""
let params = ""

if (!serviceType || !device || !sourceModel) {
return null
}

switch(serviceType) {
case "acsystem":
url = "/pages/settings/devicelist/rs/PageRsSystem.qml"
params = { "bindPrefix" : device.serviceUid }
summary = [ Global.system.systemStateToText(device.state) ]
break;

case "vebus":
// vebus devices may also show up as AC inputs or batteries, so ensure they do not
// appear multiple times in the list.
if (sourceModel !== Global.inverterChargers.veBusDevices) {
return null
} else {
url = "/pages/vebusdevice/PageVeBus.qml"
params = { "veBusDevice" : device }
summary = [ Global.system.systemStateToText(device.state) ]
}
break;

case "multi":
// multi devices are not shown in the Device List; they are shown as part of the
// "Devices" list in the acsystem page (PageRsSystem) instead.
return null

case "battery":
url = "/pages/settings/devicelist/battery/PageBattery.qml"
params = { "battery" : device }
summary = (!device.isParallelBms && device.state === VenusOS.Battery_State_Pending)
? [
CommonWords.pending,
Units.getCombinedDisplayText(VenusOS.Units_Volt_DC, device.voltage),
Units.getCombinedDisplayText(VenusOS.Units_Percentage, device.stateOfCharge)
]
: [
Units.getCombinedDisplayText(VenusOS.Units_Percentage, device.stateOfCharge),
Units.getCombinedDisplayText(VenusOS.Units_Volt_DC, device.voltage),
Units.getCombinedDisplayText(VenusOS.Units_Amp, device.current),
]
break;

case "solarcharger":
url = "/pages/solar/SolarChargerPage.qml"
params = { "solarCharger" : device }
summary = [
device.errorCode <= 0
? Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power)
//: %1 = error number
//% "Error: #%1"
: qsTrId("devicelist_solarcharger_error").arg(device.errorCode)
]
break;

case "charger":
url = "/pages/settings/devicelist/PageAcCharger.qml"
params = { "bindPrefix" : device.serviceUid }
summary = [ Global.system.systemStateToText(device.state) ]
break;

case "tank":
url = "/pages/settings/devicelist/tank/PageTankSensor.qml"
params = { "bindPrefix" : device.serviceUid }
GradientListView {
model: Global.allDevicesModel

if (isNaN(device.level)) {
summary = [ device.status === VenusOS.Tank_Status_Unknown ? "--" : Global.tanks.statusToText(device.status) ]
} else {
const levelText = Units.getCombinedDisplayText(VenusOS.Units_Percentage, device.level)
if (isNaN(device.temperature)) {
summary = [ levelText ]
delegate: Loader {
id: delegateLoader

required property bool connected
required property BaseDevice device
required property BaseDeviceModel sourceModel
required property string cachedDeviceName

readonly property bool _loadCustomDelegate: connected && !!device

// Only set width; height is sized to the loaded item, in case allowed=false and the
// item should not be visible.
width: parent ? parent.width : 0

on_LoadCustomDelegateChanged: {
let delegateUri
if (_loadCustomDelegate) {
const serviceType = BackendConnection.serviceTypeFromUid(device.serviceUid)
if (!serviceType) {
console.warn("DeviceList: cannot load delegate, cannot read service type from serviceUid:", device.serviceUid)
return
}
setSource("delegates/DeviceListDelegate_%1.qml".arg(serviceType), {
device: Qt.binding(function() { return delegateLoader.device }),
sourceModel: Qt.binding(function() { return delegateLoader.sourceModel }),
})
} else {
const tankTemp = Global.systemSettings.convertFromCelsius(device.temperature)
summary = [
Units.getCombinedDisplayText(Global.systemSettings.temperatureUnit, tankTemp),
levelText
]
setSource("delegates/DisconnectedDeviceListDelegate.qml", {
cachedDeviceName: Qt.binding(function() { return delegateLoader.cachedDeviceName }),
})
}
}
break;

case "genset": // deliberate fall through
case "dcgenset":
url = "/pages/settings/devicelist/PageGenset.qml"
params = { "bindPrefix": device.serviceUid }

const gensetPowerText = Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power)
if (device.gensetStatusCode >= 0) {
summary = [ Global.acInputs.gensetStatusCodeToText(device.gensetStatusCode), gensetPowerText ]
} else {
summary = [ gensetPowerText ]
}
break;

case "pvinverter": // deliberate fall through
case "grid": // deliberate fall through
case "heatpump": // deliberate fall through
case "acload":
url = "/pages/settings/devicelist/ac-in/PageAcIn.qml"
params = { "bindPrefix": device.serviceUid }

const acInputPowerText = Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power)
if (device.gensetStatusCode >= 0) {
summary = [ Global.acInputs.gensetStatusCodeToText(device.gensetStatusCode), acInputPowerText ]
} else {
summary = [ acInputPowerText ]
}
break;

case "motordrive":
url = "/pages/settings/devicelist/PageMotorDrive.qml"
params = { "bindPrefix" : device.serviceUid }
summary = [ Units.getCombinedDisplayText(VenusOS.Units_RevolutionsPerMinute, device.motorRpm) ]
break;

case "inverter":
url = "/pages/settings/devicelist/inverter/PageInverter.qml"
params = { "bindPrefix" : device.serviceUid }
summary = [ Units.getCombinedDisplayText(device.currentPhase.powerUnit, device.currentPhase.power) ]
break;

case "temperature":
url = "/pages/settings/devicelist/temperature/PageTemperatureSensor.qml"
params = { "bindPrefix" : device.serviceUid }

const inputTemp = Global.systemSettings.convertFromCelsius(device.temperature)
if (isNaN(device.humidity)) {
summary = [
Units.getCombinedDisplayText(Global.systemSettings.temperatureUnit, inputTemp, 0),
]
} else {
summary = [
Units.getCombinedDisplayText(Global.systemSettings.temperatureUnit, inputTemp, 0),
Units.getCombinedDisplayText(VenusOS.Units_Percentage, device.humidity),
]
}
break;

case "digitalinput":
url = "/pages/settings/devicelist/PageDigitalInput.qml"
params = {"bindPrefix": device.serviceUid }
summary = [ Global.digitalInputs.inputStateToText(device.state) ]
break;

case "evcharger":
url = "/pages/evcs/EvChargerPage.qml"
params = { "evCharger" : device }

const evChargerModeText = Global.evChargers.chargerModeToText(device.mode)
if (device.mode < 0) {
summary = [ Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power) ]
} else if (device.status === VenusOS.Evcs_Status_Charging) {
summary = [ evChargerModeText, Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power) ]
} else if (device.status >= 0) {
summary = [ evChargerModeText, Global.evChargers.chargerStatusToText(device.status) ]
} else {
summary = [ evChargerModeText ]
onStatusChanged: {
if (status === Loader.Error) {
console.log("Failed to load Device List delegate for '%1' service from file: %2"
.arg(BackendConnection.serviceTypeFromUid(device.serviceUid))
.arg(source))
}
}
break;

case "fuelcell": // deliberate fall through
case "dcsource": // deliberate fall through
case "dcload": // deliberate fall through
case "dcsystem": // deliberate fall through
case "dcdc": // deliberate fall through
case "alternator":
url = serviceType === "alternator" ? "/pages/settings/devicelist/dc-in/PageAlternator.qml"
: "/pages/settings/devicelist/dc-in/PageDcMeter.qml"
params = {"bindPrefix": device.serviceUid }
summary = [
Units.getCombinedDisplayText(VenusOS.Units_Volt_DC, device.voltage),
Units.getCombinedDisplayText(VenusOS.Units_Amp, device.current),
Units.getCombinedDisplayText(VenusOS.Units_Watt, device.power),
]
break;

case "pulsemeter":
url = "/pages/settings/devicelist/pulsemeter/PagePulseCounter.qml"
params = {"bindPrefix": device.serviceUid }
summary = [ Units.getCombinedDisplayText(Global.systemSettings.volumeUnit, device.aggregate) ]
break;

case "unsupported":
//: Device is not supported
//% "Unsupported"
summary = [ qsTrId("devicelist_unsupported") ]
url = "/pages/settings/devicelist/PageUnsupportedDevice.qml"
params = { "bindPrefix": device.serviceUid }
break;

case "meteo":
url = "/pages/settings/devicelist/PageMeteo.qml"
params = {"bindPrefix": device.serviceUid }
summary = [ Units.getCombinedDisplayText(VenusOS.Units_WattsPerSquareMeter, device.irradiance) ]
break;

default:
return null
}
params.title = Qt.binding(function(){ return device.name })

return { "summary": summary, "url": url, "params": params }
}

GradientListView {
model: Global.allDevicesModel

footer: ListButton {
//% "Remove disconnected devices"
Expand All @@ -237,47 +63,5 @@ Page {
Global.allDevicesModel.removeDisconnectedDevices()
}
}

delegate: ListTextGroup {
id: deviceDelegate

readonly property string _serviceType: model.device
? BackendConnection.type === BackendConnection.MqttSource
? model.device.serviceUid.split("/")[1] || "" // serviceUid = mqtt/<serviceType>/<path>
: model.device.serviceUid.split(".")[2] || "" // serviceUid = dbus/com.victronenergy.<serviceType>[.suffix]/<path>
: ""
readonly property var _displayInfo: model.connected
? root._deviceDisplayInfo(_serviceType, model.device, model.sourceModel)
: null

text: model.cachedDeviceName
textModel: model.connected && _displayInfo ? _displayInfo.summary || [] : [ CommonWords.not_connected ]
down: deviceMouseArea.containsPress
allowed: !model.connected || _displayInfo !== null

CP.ColorImage {
parent: deviceDelegate.content
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/images/icon_arrow_32.svg"
rotation: 180
color: deviceMouseArea.containsPress ? Theme.color_listItem_down_forwardIcon : Theme.color_listItem_forwardIcon
visible: deviceMouseArea.enabled
}

ListPressArea {
id: deviceMouseArea

anchors {
fill: parent
bottomMargin: deviceDelegate.spacing
}
radius: deviceDelegate.backgroundRect.radius
enabled: !!_displayInfo && _displayInfo.url.length > 0
onClicked: {
Global.pageManager.pushPage(_displayInfo.url, _displayInfo.params)
}
}
}
}
}

Loading

0 comments on commit 5eb895b

Please sign in to comment.