Skip to content

Commit

Permalink
Rework trackers and improve SolarDeviceListPage performance
Browse files Browse the repository at this point in the history
Add SolarInputModel to store the data for each tracker and PV
inverter entry in the list. This allows the ListView to just create
one list item for each entry in the model, instead of creating complex
delegates with Column+Repeater objects when showing multiple trackers
for a single solarcharger/multi/inverter service. Also, the model
sorts its items to allow the use of ListView sections for creating the
column headers.

This makes the list load and scroll more quickly, especially when it
contains a large number of trackers.

Also, remove the tracker list from SolarDevice.qml as it is only
required in specific pages, and those can load their own SolarTracker
objects when needed.

Rename SolarDeviceListPage to SolarInputListPage, as it shows a list
of trackers and PV inverters, rather than a list of SolarDevice items.

Fixes #1848
  • Loading branch information
blammit committed Jan 28, 2025
1 parent 27ec557 commit 5e21731
Show file tree
Hide file tree
Showing 12 changed files with 499 additions and 222 deletions.
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ set (VENUS_QML_MODULE_SOURCES
data/common/SolarDevice.qml
data/common/SolarHistory.qml
data/common/SolarHistoryErrorModel.qml
data/common/SolarTracker.qml
data/common/SolarTrackerDailyHistory.qml
data/common/SystemBattery.qml
data/common/Tank.qml
Expand Down Expand Up @@ -640,8 +641,8 @@ set (VENUS_QML_MODULE_SOURCES
pages/solar/PageSolarParallelOperation.qml
pages/solar/PvInverterPage.qml
pages/solar/SolarDevicePage.qml
pages/solar/SolarDeviceListPage.qml
pages/solar/SolarHistoryPage.qml
pages/solar/SolarInputListPage.qml
pages/vebusdevice/PageAcSensor.qml
pages/vebusdevice/PageAcSensors.qml
pages/vebusdevice/PageVeBusAdvanced.qml
Expand Down Expand Up @@ -704,6 +705,8 @@ list(APPEND VenusQMLModule_CPP_SOURCES
src/units.cpp
src/screenblanker.h
src/screenblanker.cpp
src/solarinputmodel.h
src/solarinputmodel.cpp
src/widgetconnectorpathupdater.h
src/widgetconnectorpathupdater.cpp
)
Expand Down
2 changes: 1 addition & 1 deletion components/widgets/SolarYieldWidget.qml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ OverviewWidget {
Global.pageManager.pushPage("/pages/solar/PvInverterPage.qml",
{ "pvInverter": Global.pvInverters.model.deviceAt(0) })
} else {
Global.pageManager.pushPage("/pages/solar/SolarDeviceListPage.qml", { "title": root.title })
Global.pageManager.pushPage("/pages/solar/SolarInputListPage.qml", { "title": root.title })
}
}

Expand Down
74 changes: 10 additions & 64 deletions data/common/SolarDevice.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ import Victron.VenusOS
Device {
id: root

readonly property ListModel trackers: ListModel {}
readonly property real power: _totalPower.isValid ? _totalPower.value : NaN
readonly property alias history: _history

// For solarcharger services, assume trackerCount=1 if /NrOfTrackers is not set.
readonly property int trackerCount: _nrOfTrackers.isValid ? _nrOfTrackers.value : (_isSolarCharger ? 1 : 0)

// This is the overall error history.
// For the per-day error history, use dailyHistory(day).errorModel
readonly property alias errorModel: _history.errorModel

readonly property bool _isSolarCharger: BackendConnection.serviceTypeFromUid(serviceUid) === "solarcharger"

signal yieldUpdatedForDay(day: int, yieldKwh: real)

function dailyHistory(day) {
Expand All @@ -31,80 +35,22 @@ Device {
return _history.dailyTrackerHistory(day, trackerIndex)
}

function trackerName(trackerIndex, format) {
const tracker = _trackerObjects.objectAt(trackerIndex)
const trackerName = tracker ? tracker.name || "" : ""
return Global.solarDevices.formatTrackerName(trackerName, trackerIndex, trackers.count, root.name, format)
}

//--- internal members below ---

readonly property VeQuickItem _totalPower: VeQuickItem {
uid: root.serviceUid + "/Yield/Power"
}

readonly property VeQuickItem _nrOfTrackers: VeQuickItem {
uid: root.serviceUid + "/NrOfTrackers"
}

//--- history ---

readonly property SolarHistory _history: SolarHistory {
id: _history
bindPrefix: root.serviceUid
deviceName: root.name
trackerCount: root.trackers.count
}

//--- Solar trackers ---

readonly property VeQuickItem _trackerCount: VeQuickItem {
uid: root.serviceUid + "/NrOfTrackers"
}

readonly property Instantiator _trackerObjects: Instantiator {
model: _trackerCount.value || 1 // there is always at least one tracker, even if NrOfTrackers is not set
delegate: QtObject {
id: tracker

readonly property int modelIndex: model.index
readonly property real power: root.trackers.count <= 1 ? root.power : _power.value || 0
readonly property real voltage: _voltage.value || 0
readonly property real current: isNaN(power) || isNaN(voltage) || voltage === 0 ? NaN : power / voltage
readonly property string name: _name.value || ""

readonly property VeQuickItem _voltage: VeQuickItem {
uid: root.trackers.count <= 1
? root.serviceUid + "/Pv/V"
: root.serviceUid + "/Pv/" + model.index + "/V"
}

readonly property VeQuickItem _power: VeQuickItem {
uid: root.trackers.count === 1
? "" // only 1 tracker, use root.power instead (i.e. same as /Yield/Power)
: root.serviceUid + "/Pv/" + model.index + "/P"
}

readonly property VeQuickItem _name: VeQuickItem {
uid: root.serviceUid + "/Pv/" + model.index + "/Name"
}
}

onObjectAdded: function(index, object) {
let insertionIndex = root.trackers.count
for (let i = 0; i < root.trackers.count; ++i) {
const sortIndex = root.trackers.get(i).solarTracker.modelIndex
if (index < sortIndex) {
insertionIndex = i
break
}
}
root.trackers.insert(insertionIndex, {"solarTracker": object})
}

onObjectRemoved: function(index, object) {
for (let i = 0; i < root.trackers.count; ++i) {
if (root.trackers.get(i).solarTracker.serviceUid === object.serviceUid) {
root.trackers.remove(i)
break
}
}
}
trackerCount: root.trackerCount
}
}
33 changes: 33 additions & 0 deletions data/common/SolarTracker.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
** Copyright (C) 2025 Victron Energy B.V.
** See LICENSE.txt for license information.
*/

import QtQuick
import Victron.VenusOS

QtObject {
id: root

required property SolarDevice device
required property int trackerIndex

readonly property string name: _name.value ?? ""
readonly property real power: _power.isValid ? _power.value : NaN
readonly property real voltage: _voltage.isValid ? _voltage.value : NaN
readonly property real current: !power || !voltage ? NaN : power / voltage

// If there is only 1 tracker (e.g. all common MPPTs), the voltage and power are provided via
// /Pv/V and /Yield/Power instead of /Pv/0/V and /Pv/0/P.
readonly property VeQuickItem _voltage: VeQuickItem {
uid: root.device.trackerCount <= 1 ? `${root.device.serviceUid}/Pv/V` : `${root.device.serviceUid}/Pv/${root.trackerIndex}/V`
}

readonly property VeQuickItem _power: VeQuickItem {
uid: root.device.trackerCount <= 1 ? `${root.device.serviceUid}/Yield/Power` : `${root.device.serviceUid}/Pv/${root.trackerIndex}/P`
}

readonly property VeQuickItem _name: VeQuickItem {
uid: root.device.trackerCount <= 1 ? "" : `${root.device.serviceUid}/Pv/${root.trackerIndex}/Name`
}
}
9 changes: 7 additions & 2 deletions data/mock/SolarDevicesImpl.qml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ QtObject {
Global.mockDataSimulator.setMockValue(serviceUid + path, value)
}

function mockValue(path) {
return Global.mockDataSimulator.mockValue(serviceUid + path)
}

function randomizeMeasurments() {
/*
1) a solar charger with one tracker has 3 paths:
Expand All @@ -57,8 +61,9 @@ QtObject {
*/

let totalPower = 0
if (_trackerCount.value > 1) {
for (let i = 0; i < _trackerCount.value; ++i) {
const trackerCount = mockValue("/NrOfTrackers")
if (trackerCount > 1) {
for (let i = 0; i < trackerCount; ++i) {
const p = Math.random() * 100
const trackerUid = serviceUid + "/Pv/" + i
Global.mockDataSimulator.setMockValue(trackerUid + "/V", 90 + (Math.random() * 10))
Expand Down
25 changes: 7 additions & 18 deletions pages/settings/devicelist/rs/PageMultiRs.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ Page {
property string bindPrefix
readonly property bool multiPhase: numberOfPhases.isValid && numberOfPhases.value >= 2 && !_phase.isValid
readonly property int trackerCount: numberOfTrackers.value || 0
readonly property alias solarDevice: device

title: device.name

Device {
SolarDevice {
id: device
serviceUid: root.bindPrefix
}
Expand Down Expand Up @@ -117,14 +118,7 @@ Page {
preferredVisible: root.trackerCount > 0
onClicked: {
Global.pageManager.pushPage("/pages/solar/SolarHistoryPage.qml",
{ "solarHistory": solarHistory })
}

SolarHistory {
id: solarHistory
bindPrefix: root.bindPrefix
deviceName: root.title
trackerCount: root.trackerCount
{ "solarHistory": root.device.history })
}
}

Expand Down Expand Up @@ -253,16 +247,11 @@ Page {
Instantiator {
id: trackerObjects
model: root.trackerCount
delegate: QtObject {
delegate: SolarTracker {
required property int index
readonly property real power: _power.isValid ? _power.value : NaN
readonly property real voltage: _voltage.isValid ? _voltage.value : NaN
readonly property real current: !_power.isValid || !_voltage.isValid || voltage === 0 ? NaN : power / voltage
readonly property string name: _name.value || ""

readonly property VeQuickItem _voltage: VeQuickItem { uid: root.bindPrefix + "/Pv/" + index + "/V" }
readonly property VeQuickItem _power: VeQuickItem { uid: root.bindPrefix + "/Pv/" + index + "/P" }
readonly property VeQuickItem _name: VeQuickItem { uid: root.bindPrefix + "/Pv/" + index + "/Name" }

device: root.solarDevice
trackerIndex: index
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion pages/solar/PageSolarCharger.qml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Page {
{ title: CommonWords.power_watts, unit: VenusOS.Units_Watt }
]
valueForModelIndex: function(trackerIndex, column) {
const tracker = root.solarDevice.trackers.get(trackerIndex).solarTracker
const tracker = trackerObjects.objectAt(trackerIndex)
if (column === 0) {
return Global.solarDevices.formatTrackerName(tracker.name, trackerIndex, root.trackerCount, root.solarDevice.name, VenusOS.TrackerName_NoDevicePrefix)
} else if (column === 1) {
Expand All @@ -172,6 +172,17 @@ Page {
return tracker.power
}
}

Instantiator {
id: trackerObjects
model: root.solarDevice.trackerCount
delegate: SolarTracker {
required property int index

device: root.solarDevice
trackerIndex: index
}
}
}

ListQuantityGroup {
Expand Down
Loading

0 comments on commit 5e21731

Please sign in to comment.