From b118f6d790e6811d6758b4c36545ca3bd3052221 Mon Sep 17 00:00:00 2001 From: kwindrem <58538395+kwindrem@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:49:23 -0700 Subject: [PATCH] add support for v3.40~36 --- .../PageMain.qml-v3.22} | 0 .../PageMain.qml-v3.22.orig} | 0 FileSets/PatchSource/PageMain.qml-v3.22.patch | 79 + .../PageMain.qml-v3.40~13} | 0 .../PageMain.qml-v3.40~13.orig} | 0 .../PatchSource/PageMain.qml-v3.40~13.patch | 75 + .../PageMain.qml-v3.40~35} | 0 .../PageMain.qml-v3.40~35.orig} | 0 .../PatchSource/PageMain.qml-v3.40~35.patch | 85 + ...attributes.csv => attributes.csv-v3.40~35} | 5 + ....csv.orig => attributes.csv-v3.40~35.orig} | 5 + ...sv.patch => attributes.csv-v3.40~35.patch} | 6 +- .../PatchSource/attributes.csv.patchOptions | 1 + .../dbus_systemcalc.py-v2.73} | 0 .../dbus_systemcalc.py-v2.73.orig} | 0 .../dbus_systemcalc.py-v2.73.patch | 300 ++++ .../dbus_systemcalc.py-v2.84} | 0 .../dbus_systemcalc.py-v2.84.orig} | 0 .../dbus_systemcalc.py-v2.84.patch | 291 ++++ .../dbus_systemcalc.py-v2.89} | 0 .../dbus_systemcalc.py-v2.89.orig} | 0 .../dbus_systemcalc.py-v2.89.patch | 385 +++++ .../dbus_systemcalc.py-v2.94} | 0 .../dbus_systemcalc.py-v2.94.orig} | 0 .../dbus_systemcalc.py-v2.94.patch | 384 +++++ .../dbus_systemcalc.py-v3.14} | 0 .../dbus_systemcalc.py-v3.14.orig} | 0 .../dbus_systemcalc.py-v3.14.patch | 402 +++++ .../dbus_systemcalc.py-v3.40~10.orig} | 0 .../dbus_systemcalc.py-v3.40~10.patch | 392 +++++ .../dbus_systemcalc.py-v3.40~15} | 0 .../dbus_systemcalc.py-v3.40~15.orig} | 0 .../dbus_systemcalc.py-v3.40~15.patch | 569 +++++++ .../dbus_systemcalc.py-v3.40~2} | 0 .../dbus_systemcalc.py-v3.40~2.orig} | 0 .../dbus_systemcalc.py-v3.40~2.patch | 413 +++++ .../dbus_systemcalc.py-v3.40~35} | 0 .../dbus_systemcalc.py-v3.40~35.orig} | 0 .../dbus_systemcalc.py-v3.40~35.patch | 574 +++++++ .../main.qml => PatchSource/main.qml-v2.94} | 0 .../main.qml-v2.94.orig} | 0 FileSets/PatchSource/main.qml-v2.94.patch | 470 ++++++ .../main.qml => PatchSource/main.qml-v3.22} | 0 .../main.qml-v3.22.orig} | 0 FileSets/PatchSource/main.qml-v3.22.patch | 477 ++++++ .../main.qml-v3.40~13} | 0 .../main.qml-v3.40~13.orig} | 0 FileSets/PatchSource/main.qml-v3.40~13.patch | 477 ++++++ .../main.qml-v3.40~35} | 0 .../main.qml-v3.40~35.orig} | 0 FileSets/PatchSource/main.qml-v3.40~35.patch | 455 +++++ FileSets/fileList | 3 - FileSets/fileListPatched | 3 + FileSets/v2.73/PageMain.qml | 251 --- FileSets/v2.73/PageMain.qml.orig | 210 --- FileSets/v2.84/LINKS_ONLY | 0 FileSets/v2.89/PageMain.qml | 274 --- FileSets/v2.89/PageMain.qml.orig | 233 --- FileSets/v2.89/main.qml | 558 ------ FileSets/v2.89/main.qml.orig | 364 ---- FileSets/v2.92/LINKS_ONLY | 0 FileSets/v2.92/dbus_systemcalc.py | 1393 --------------- FileSets/v2.92/dbus_systemcalc.py.orig | 1193 ------------- FileSets/v2.94/PageMain.qml | 281 --- FileSets/v2.94/PageMain.qml.orig | 240 --- FileSets/v3.00/LINKS_ONLY | 0 FileSets/v3.00/dbus_systemcalc.py | 1341 --------------- FileSets/v3.00/dbus_systemcalc.py.orig | 1143 ------------- FileSets/v3.00~32/dbus_systemcalc.py | 1341 --------------- FileSets/v3.00~32/dbus_systemcalc.py.orig | 1143 ------------- FileSets/v3.01/dbus_systemcalc.py | 1343 --------------- FileSets/v3.01/dbus_systemcalc.py.orig | 1145 ------------- FileSets/v3.01/main.qml | 582 ------- FileSets/v3.01/main.qml.orig | 388 ----- FileSets/v3.10/dbus_systemcalc.py | 1344 --------------- FileSets/v3.10/dbus_systemcalc.py.orig | 1146 ------------- FileSets/v3.10/main.qml | 582 ------- FileSets/v3.10/main.qml.orig | 388 ----- FileSets/v3.12/LINKS_ONLY | 0 FileSets/v3.12/dbus_systemcalc.py | 1344 --------------- FileSets/v3.12/dbus_systemcalc.py.orig | 1146 ------------- FileSets/v3.14/PageMain.qml | 280 --- FileSets/v3.14/PageMain.qml.orig | 239 --- FileSets/v3.22/dbus_systemcalc.py | 1370 --------------- FileSets/v3.22/dbus_systemcalc.py.orig | 1172 ------------- FileSets/v3.40~1/LINKS_ONLY | 0 FileSets/v3.40~1/dbus_systemcalc.py | 1371 --------------- FileSets/v3.40~1/dbus_systemcalc.py.orig | 1173 ------------- FileSets/v3.40~10/dbus_systemcalc.py | 1488 ---------------- FileSets/v3.40~13/dbus_systemcalc.py | 1489 ---------------- FileSets/v3.40~13/dbus_systemcalc.py.orig | 1246 -------------- FileSets/v3.40~15/LINKS_ONLY | 0 FileSets/v3.40~16/LINKS_ONLY | 0 FileSets/v3.40~16/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~16/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~2/LINKS_ONLY | 0 FileSets/v3.40~20/LINKS_ONLY | 0 FileSets/v3.40~20/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~20/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~27/LINKS_ONLY | 0 FileSets/v3.40~27/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~27/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~29/LINKS_ONLY | 0 FileSets/v3.40~29/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~29/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~30/LINKS_ONLY | 0 FileSets/v3.40~30/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~30/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~33/LINKS_ONLY | 0 FileSets/v3.40~33/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~33/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~34/LINKS_ONLY | 0 FileSets/v3.40~34/dbus_systemcalc.py | 1500 ----------------- FileSets/v3.40~34/dbus_systemcalc.py.orig | 1256 -------------- FileSets/v3.40~6/LINKS_ONLY | 0 FileSets/v3.40~6/dbus_systemcalc.py | 1488 ---------------- FileSets/v3.40~6/dbus_systemcalc.py.orig | 1287 -------------- changes | 4 + version | 2 +- 119 files changed, 5850 insertions(+), 51275 deletions(-) rename FileSets/{v3.22/PageMain.qml => PatchSource/PageMain.qml-v3.22} (100%) rename FileSets/{v3.22/PageMain.qml.orig => PatchSource/PageMain.qml-v3.22.orig} (100%) create mode 100644 FileSets/PatchSource/PageMain.qml-v3.22.patch rename FileSets/{v3.40~13/PageMain.qml => PatchSource/PageMain.qml-v3.40~13} (100%) rename FileSets/{v3.40~13/PageMain.qml.orig => PatchSource/PageMain.qml-v3.40~13.orig} (100%) create mode 100644 FileSets/PatchSource/PageMain.qml-v3.40~13.patch rename FileSets/{v3.40~35/PageMain.qml => PatchSource/PageMain.qml-v3.40~35} (100%) rename FileSets/{v3.40~35/PageMain.qml.orig => PatchSource/PageMain.qml-v3.40~35.orig} (100%) create mode 100644 FileSets/PatchSource/PageMain.qml-v3.40~35.patch rename FileSets/PatchSource/{attributes.csv => attributes.csv-v3.40~35} (99%) rename FileSets/PatchSource/{attributes.csv.orig => attributes.csv-v3.40~35.orig} (99%) rename FileSets/PatchSource/{attributes.csv.patch => attributes.csv-v3.40~35.patch} (83%) create mode 100644 FileSets/PatchSource/attributes.csv.patchOptions rename FileSets/{v2.73/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v2.73} (100%) rename FileSets/{v2.73/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v2.73.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v2.73.patch rename FileSets/{v2.84/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v2.84} (100%) rename FileSets/{v2.84/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v2.84.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v2.84.patch rename FileSets/{v2.89/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v2.89} (100%) rename FileSets/{v2.89/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v2.89.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v2.89.patch rename FileSets/{v2.94/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v2.94} (100%) rename FileSets/{v2.94/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v2.94.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v2.94.patch rename FileSets/{v3.14/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v3.14} (100%) rename FileSets/{v3.14/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v3.14.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch rename FileSets/{v3.40~10/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v3.40~10.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.patch rename FileSets/{v3.40~15/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v3.40~15} (100%) rename FileSets/{v3.40~15/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v3.40~15.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.patch rename FileSets/{v3.40~2/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v3.40~2} (100%) rename FileSets/{v3.40~2/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v3.40~2.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.patch rename FileSets/{v3.40~35/dbus_systemcalc.py => PatchSource/dbus_systemcalc.py-v3.40~35} (100%) rename FileSets/{v3.40~35/dbus_systemcalc.py.orig => PatchSource/dbus_systemcalc.py-v3.40~35.orig} (100%) create mode 100644 FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.patch rename FileSets/{v2.94/main.qml => PatchSource/main.qml-v2.94} (100%) rename FileSets/{v2.94/main.qml.orig => PatchSource/main.qml-v2.94.orig} (100%) create mode 100644 FileSets/PatchSource/main.qml-v2.94.patch rename FileSets/{v3.22/main.qml => PatchSource/main.qml-v3.22} (100%) rename FileSets/{v3.22/main.qml.orig => PatchSource/main.qml-v3.22.orig} (100%) create mode 100644 FileSets/PatchSource/main.qml-v3.22.patch rename FileSets/{v3.40~13/main.qml => PatchSource/main.qml-v3.40~13} (100%) rename FileSets/{v3.40~13/main.qml.orig => PatchSource/main.qml-v3.40~13.orig} (100%) create mode 100644 FileSets/PatchSource/main.qml-v3.40~13.patch rename FileSets/{v3.40~35/main.qml => PatchSource/main.qml-v3.40~35} (100%) rename FileSets/{v3.40~35/main.qml.orig => PatchSource/main.qml-v3.40~35.orig} (100%) create mode 100644 FileSets/PatchSource/main.qml-v3.40~35.patch delete mode 100644 FileSets/v2.73/PageMain.qml delete mode 100644 FileSets/v2.73/PageMain.qml.orig create mode 100644 FileSets/v2.84/LINKS_ONLY delete mode 100644 FileSets/v2.89/PageMain.qml delete mode 100644 FileSets/v2.89/PageMain.qml.orig delete mode 100644 FileSets/v2.89/main.qml delete mode 100644 FileSets/v2.89/main.qml.orig create mode 100644 FileSets/v2.92/LINKS_ONLY delete mode 100755 FileSets/v2.92/dbus_systemcalc.py delete mode 100755 FileSets/v2.92/dbus_systemcalc.py.orig delete mode 100644 FileSets/v2.94/PageMain.qml delete mode 100644 FileSets/v2.94/PageMain.qml.orig create mode 100644 FileSets/v3.00/LINKS_ONLY delete mode 100755 FileSets/v3.00/dbus_systemcalc.py delete mode 100755 FileSets/v3.00/dbus_systemcalc.py.orig delete mode 100755 FileSets/v3.00~32/dbus_systemcalc.py delete mode 100755 FileSets/v3.00~32/dbus_systemcalc.py.orig delete mode 100755 FileSets/v3.01/dbus_systemcalc.py delete mode 100755 FileSets/v3.01/dbus_systemcalc.py.orig delete mode 100644 FileSets/v3.01/main.qml delete mode 100644 FileSets/v3.01/main.qml.orig delete mode 100755 FileSets/v3.10/dbus_systemcalc.py delete mode 100755 FileSets/v3.10/dbus_systemcalc.py.orig delete mode 100644 FileSets/v3.10/main.qml delete mode 100644 FileSets/v3.10/main.qml.orig create mode 100644 FileSets/v3.12/LINKS_ONLY delete mode 100755 FileSets/v3.12/dbus_systemcalc.py delete mode 100755 FileSets/v3.12/dbus_systemcalc.py.orig delete mode 100644 FileSets/v3.14/PageMain.qml delete mode 100644 FileSets/v3.14/PageMain.qml.orig delete mode 100755 FileSets/v3.22/dbus_systemcalc.py delete mode 100755 FileSets/v3.22/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~1/LINKS_ONLY delete mode 100755 FileSets/v3.40~1/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~1/dbus_systemcalc.py.orig delete mode 100755 FileSets/v3.40~10/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~13/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~13/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~15/LINKS_ONLY create mode 100644 FileSets/v3.40~16/LINKS_ONLY delete mode 100755 FileSets/v3.40~16/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~16/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~2/LINKS_ONLY create mode 100644 FileSets/v3.40~20/LINKS_ONLY delete mode 100755 FileSets/v3.40~20/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~20/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~27/LINKS_ONLY delete mode 100755 FileSets/v3.40~27/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~27/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~29/LINKS_ONLY delete mode 100755 FileSets/v3.40~29/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~29/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~30/LINKS_ONLY delete mode 100755 FileSets/v3.40~30/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~30/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~33/LINKS_ONLY delete mode 100755 FileSets/v3.40~33/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~33/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~34/LINKS_ONLY delete mode 100755 FileSets/v3.40~34/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~34/dbus_systemcalc.py.orig create mode 100644 FileSets/v3.40~6/LINKS_ONLY delete mode 100755 FileSets/v3.40~6/dbus_systemcalc.py delete mode 100755 FileSets/v3.40~6/dbus_systemcalc.py.orig diff --git a/FileSets/v3.22/PageMain.qml b/FileSets/PatchSource/PageMain.qml-v3.22 similarity index 100% rename from FileSets/v3.22/PageMain.qml rename to FileSets/PatchSource/PageMain.qml-v3.22 diff --git a/FileSets/v3.22/PageMain.qml.orig b/FileSets/PatchSource/PageMain.qml-v3.22.orig similarity index 100% rename from FileSets/v3.22/PageMain.qml.orig rename to FileSets/PatchSource/PageMain.qml-v3.22.orig diff --git a/FileSets/PatchSource/PageMain.qml-v3.22.patch b/FileSets/PatchSource/PageMain.qml-v3.22.patch new file mode 100644 index 00000000..182cce70 --- /dev/null +++ b/FileSets/PatchSource/PageMain.qml-v3.22.patch @@ -0,0 +1,79 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.22.orig 2024-07-08 09:45:00 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.22 2024-01-24 10:19:41 +@@ -1,11 +1,52 @@ ++//////// GuiMods modified order to put Settings, then Notifications at top of list ++ + import QtQuick 1.1 ++import "utils.js" as Utils + import com.victron.velib 1.0 + + MbPage { + id: root + title: qsTr("Device List") + ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} ++ property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 ++ + model: VisualModels { ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ VisibleItemModel { ++ MbSubMenu { ++ description: qsTr("Settings") ++ subpage: Component { PageSettings {} } ++ show: settingsAtTop ++ } ++ ++ MbSubMenu { ++ id: menuNotificationsTop ++ description: qsTr("Notifications") ++ item: VBusItem { ++ property variant active: NotificationCenter.notifications.filter( ++ function isActive(obj) { return obj.active} ) ++ value: active.length > 0 ? active.length : "" ++ } ++ subpage: Component { PageNotifications {} } ++ show: settingsAtTop ++ } ++ ++ MbOK { ++ description: qsTr("Remove disconnected devices") ++ value: qsTr("Press to remove") ++ show: settingsAtTop && deviceList.disconnectedDevices != 0 ++ editable: true ++ ++ function clicked() { ++ listview.decrementCurrentIndex() ++ deviceList.removeDisconnected() ++ } ++ } ++ } ++//////// end GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ + VisualDataModel { + model: VeSortFilterProxyModel { + model: DeviceList { +@@ -37,17 +78,22 @@ + value: active.length > 0 ? active.length : "" + } + subpage: Component { PageNotifications {} } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbSubMenu { + description: qsTr("Settings") + subpage: Component { PageSettings {} } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbOK { + description: qsTr("Remove disconnected devices") + value: qsTr("Press to remove") +- show: deviceList.disconnectedDevices != 0 ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop && deviceList.disconnectedDevices != 0 + editable: true + + function clicked() { diff --git a/FileSets/v3.40~13/PageMain.qml b/FileSets/PatchSource/PageMain.qml-v3.40~13 similarity index 100% rename from FileSets/v3.40~13/PageMain.qml rename to FileSets/PatchSource/PageMain.qml-v3.40~13 diff --git a/FileSets/v3.40~13/PageMain.qml.orig b/FileSets/PatchSource/PageMain.qml-v3.40~13.orig similarity index 100% rename from FileSets/v3.40~13/PageMain.qml.orig rename to FileSets/PatchSource/PageMain.qml-v3.40~13.orig diff --git a/FileSets/PatchSource/PageMain.qml-v3.40~13.patch b/FileSets/PatchSource/PageMain.qml-v3.40~13.patch new file mode 100644 index 00000000..9026a911 --- /dev/null +++ b/FileSets/PatchSource/PageMain.qml-v3.40~13.patch @@ -0,0 +1,75 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.40~13.orig 2024-07-08 09:45:00 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.40~13 2024-05-14 07:00:12 +@@ -1,11 +1,48 @@ ++//////// GuiMods modified order to put Settings, then Notifications at top of list ++ + import QtQuick 1.1 ++import "utils.js" as Utils + import com.victron.velib 1.0 + + MbPage { + id: root + title: qsTr("Device List") + ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} ++ property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 ++ + model: VisualModels { ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ VisibleItemModel { ++ MbSubMenu { ++ description: qsTr("Settings") ++ subpage: Component { PageSettings {} } ++ show: settingsAtTop ++ } ++ ++ MbSubMenu { ++ id: menuNotificationsTop ++ description: qsTr("Notifications") ++ item: VBusItem { value: menuNotifications.subpage.summary } ++ subpage: PageNotifications { } ++ show: settingsAtTop ++ } ++ ++ MbOK { ++ description: qsTr("Remove disconnected devices") ++ value: qsTr("Press to remove") ++ show: settingsAtTop && deviceList.disconnectedDevices != 0 ++ editable: true ++ ++ function clicked() { ++ listview.decrementCurrentIndex() ++ deviceList.removeDisconnected() ++ } ++ } ++ } ++//////// end GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ + VisualDataModel { + model: VeSortFilterProxyModel { + model: DeviceList { +@@ -33,17 +70,22 @@ + description: qsTr("Notifications") + item: VBusItem { value: menuNotifications.subpage.summary } + subpage: PageNotifications { } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbSubMenu { + description: qsTr("Settings") + subpage: Component { PageSettings {} } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbOK { + description: qsTr("Remove disconnected devices") + value: qsTr("Press to remove") +- show: deviceList.disconnectedDevices != 0 ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop && deviceList.disconnectedDevices != 0 + editable: true + + function clicked() { diff --git a/FileSets/v3.40~35/PageMain.qml b/FileSets/PatchSource/PageMain.qml-v3.40~35 similarity index 100% rename from FileSets/v3.40~35/PageMain.qml rename to FileSets/PatchSource/PageMain.qml-v3.40~35 diff --git a/FileSets/v3.40~35/PageMain.qml.orig b/FileSets/PatchSource/PageMain.qml-v3.40~35.orig similarity index 100% rename from FileSets/v3.40~35/PageMain.qml.orig rename to FileSets/PatchSource/PageMain.qml-v3.40~35.orig diff --git a/FileSets/PatchSource/PageMain.qml-v3.40~35.patch b/FileSets/PatchSource/PageMain.qml-v3.40~35.patch new file mode 100644 index 00000000..ed9fc280 --- /dev/null +++ b/FileSets/PatchSource/PageMain.qml-v3.40~35.patch @@ -0,0 +1,85 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.40~35.orig 2024-07-08 09:45:00 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/PageMain.qml-v3.40~35 2024-05-17 07:21:07 +@@ -1,11 +1,48 @@ ++//////// GuiMods modified order to put Settings, then Notifications at top of list ++ + import QtQuick 1.1 ++import "utils.js" as Utils + import com.victron.velib 1.0 + + MbPage { + id: root + title: qsTr("Device List") + ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} ++ property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 ++ + model: VisualModels { ++//////// GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ VisibleItemModel { ++ MbSubMenu { ++ description: qsTr("Settings") ++ subpage: Component { PageSettings {} } ++ show: settingsAtTop ++ } ++ ++ MbSubMenu { ++ id: menuNotificationsTop ++ description: qsTr("Notifications") ++ item: VBusItem { value: menuNotifications.subpage.summary } ++ subpage: PageNotifications { } ++ show: settingsAtTop ++ } ++ ++ MbOK { ++ description: qsTr("Remove disconnected devices") ++ value: qsTr("Press to remove") ++ show: settingsAtTop && deviceList.disconnectedDevices != 0 ++ editable: true ++ ++ function clicked() { ++ listview.decrementCurrentIndex() ++ deviceList.removeDisconnected() ++ } ++ } ++ } ++//////// end GuiMods put Settings, Notifications, Remove disconnected... at top of list ++ + VisualDataModel { + model: VeSortFilterProxyModel { + model: DeviceList { +@@ -33,17 +70,22 @@ + description: qsTr("Notifications") + item: VBusItem { value: menuNotifications.subpage.summary } + subpage: PageNotifications { } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbSubMenu { + description: qsTr("Settings") + subpage: Component { PageSettings {} } ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop + } + + MbOK { + description: qsTr("Remove disconnected devices") + value: qsTr("Press to remove") +- show: deviceList.disconnectedDevices != 0 ++//////// GuiMods hide this if added at top ++ show: !settingsAtTop && deviceList.disconnectedDevices != 0 + editable: true + + function clicked() { +@@ -155,7 +197,8 @@ + page = vebusPage + break; + case DBusService.DBUS_SERVICE_MULTI_RS: +- return; ++ page = multiRsPage ++ break; + case DBusService.DBUS_SERVICE_BATTERY: + page = batteryPage + break; diff --git a/FileSets/PatchSource/attributes.csv b/FileSets/PatchSource/attributes.csv-v3.40~35 similarity index 99% rename from FileSets/PatchSource/attributes.csv rename to FileSets/PatchSource/attributes.csv-v3.40~35 index cae6ebdb..6dc22b1d 100644 --- a/FileSets/PatchSource/attributes.csv +++ b/FileSets/PatchSource/attributes.csv-v3.40~35 @@ -197,6 +197,11 @@ com.victronenergy.battery,/System/MaxVoltageCellId,s,,1310,string[4],1,R com.victronenergy.battery,/System/MinTemperatureCellId,s,,1314,string[4],1,R com.victronenergy.battery,/System/MaxTemperatureCellId,s,,1318,string[4],1,R com.victronenergy.battery,/Mode,i,3=On;252=Standby,1319,uint16,1,W +com.victronenergy.battery,/Balancer/Status,u,0=Unknown;1=Balanced;2=Balancing;3=Cell imbalance,1320,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/Communication,u,0=OK;1=Error,1321,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/Voltage,u,0=OK;1=Error,1322,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/NrOfBatteries,u,0=OK;1=Error,1323,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/InvalidConfiguration,u,0=OK;1=Error,1324,uint16,1,R com.victronenergy.solarcharger,/Dc/0/Voltage,d,V DC,771,uint16,100,R com.victronenergy.solarcharger,/Dc/0/Current,d,A DC,772,int16,10,R com.victronenergy.solarcharger,/Dc/0/Temperature,d,Degrees celsius,773,int16,10,R diff --git a/FileSets/PatchSource/attributes.csv.orig b/FileSets/PatchSource/attributes.csv-v3.40~35.orig similarity index 99% rename from FileSets/PatchSource/attributes.csv.orig rename to FileSets/PatchSource/attributes.csv-v3.40~35.orig index a0871899..a68d0532 100644 --- a/FileSets/PatchSource/attributes.csv.orig +++ b/FileSets/PatchSource/attributes.csv-v3.40~35.orig @@ -197,6 +197,11 @@ com.victronenergy.battery,/System/MaxVoltageCellId,s,,1310,string[4],1,R com.victronenergy.battery,/System/MinTemperatureCellId,s,,1314,string[4],1,R com.victronenergy.battery,/System/MaxTemperatureCellId,s,,1318,string[4],1,R com.victronenergy.battery,/Mode,i,3=On;252=Standby,1319,uint16,1,W +com.victronenergy.battery,/Balancer/Status,u,0=Unknown;1=Balanced;2=Balancing;3=Cell imbalance,1320,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/Communication,u,0=OK;1=Error,1321,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/Voltage,u,0=OK;1=Error,1322,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/NrOfBatteries,u,0=OK;1=Error,1323,uint16,1,R +com.victronenergy.battery,/Errors/SmartLithium/InvalidConfiguration,u,0=OK;1=Error,1324,uint16,1,R com.victronenergy.solarcharger,/Dc/0/Voltage,d,V DC,771,uint16,100,R com.victronenergy.solarcharger,/Dc/0/Current,d,A DC,772,int16,10,R com.victronenergy.solarcharger,/Dc/0/Temperature,d,Degrees celsius,773,int16,10,R diff --git a/FileSets/PatchSource/attributes.csv.patch b/FileSets/PatchSource/attributes.csv-v3.40~35.patch similarity index 83% rename from FileSets/PatchSource/attributes.csv.patch rename to FileSets/PatchSource/attributes.csv-v3.40~35.patch index 29713f28..47436c4a 100644 --- a/FileSets/PatchSource/attributes.csv.patch +++ b/FileSets/PatchSource/attributes.csv-v3.40~35.patch @@ -1,5 +1,5 @@ ---- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/attributes.csv.orig 2024-06-28 07:42:21 -+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/attributes.csv 2024-06-14 08:59:55 -@@ -485 +485 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/attributes.csv-v3.40~35.orig 2024-07-08 00:41:45 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/attributes.csv-v3.40~35 2024-07-08 09:11:43 +@@ -490 +490 @@ -com.victronenergy.digitalinput,/Type,i,2=Door;3=Bilge pump;4=Bilge alarm;5=Burglar alarm;6=Smoke alarm;7=Fire alarm;8=CO2 alarm;9=Generator,3424,uint16,1,R +com.victronenergy.digitalinput,/Type,i,2=Door;3=Bilge pump;4=Bilge alarm;5=Burglar alarm;6=Smoke alarm;7=Fire alarm;8=CO2 alarm;9=Generator,3424,uint16,1,R;11=ExtTransferSwitch,3424,uint16,1,R diff --git a/FileSets/PatchSource/attributes.csv.patchOptions b/FileSets/PatchSource/attributes.csv.patchOptions new file mode 100644 index 00000000..81753af0 --- /dev/null +++ b/FileSets/PatchSource/attributes.csv.patchOptions @@ -0,0 +1 @@ +-U 0 \ No newline at end of file diff --git a/FileSets/v2.73/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v2.73 similarity index 100% rename from FileSets/v2.73/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v2.73 diff --git a/FileSets/v2.73/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v2.73.orig similarity index 100% rename from FileSets/v2.73/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v2.73.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v2.73.patch b/FileSets/PatchSource/dbus_systemcalc.py-v2.73.patch new file mode 100644 index 00000000..1d7237cc --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v2.73.patch @@ -0,0 +1,300 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.73.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.73 2023-08-31 21:32:44 +@@ -1,6 +1,8 @@ + #!/usr/bin/python -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import gobject +@@ -73,6 +75,23 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L2/P': dummy, + '/Ac/Out/L3/P': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L2/I': dummy, ++ '/Ac/Out/L3/I': dummy, ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -101,7 +120,17 @@ + '/DeviceType' : dummy, + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, +- '/Ac/L3/Power': dummy}, ++ '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -111,6 +140,17 @@ + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, + '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -130,6 +170,11 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy, + } +@@ -254,7 +299,50 @@ + '/Dc/Vebus/Power': {'gettext': '%.0F W'}, + '/Dc/System/Power': {'gettext': '%.0F W'}, + '/Ac/ActiveIn/Source': {'gettext': '%s'}, +- '/VebusService': {'gettext': '%s'} ++ '/VebusService': {'gettext': '%s'}, ++#### added for GuiMods ++ '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -693,6 +781,13 @@ + + # ===== GRID METERS & CONSUMPTION ==== + consumption = { "L1" : None, "L2" : None, "L3" : None } ++#### added for GuiMods ++ currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type in ['Grid', 'Genset']: + servicename = 'com.victronenergy.%s' % device_type.lower() + energy_meter = self._get_first_connected_service(servicename) +@@ -709,30 +804,73 @@ + for phase in consumption: + p = None + pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) ++#### added for GuiMods ++ mc = None ++ pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) + if em_service is not None: + p = self._dbusmonitor.get_value(em_service, '/Ac/%s/Power' % phase) ++#### added for GuiMods ++ mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None ++#### added for GuiMods ++ cc = None + if uses_active_input: + ac_in = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase) + if ac_in is not None: +- c = _safeadd(c, -ac_in) ++ try: ++ c = _safeadd(c, -ac_in) ++#### added for GuiMods ++ cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ except TypeError: ++ pass ++ + # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), + # it will still be used, because there may also be a load in the same ACIn consuming + # power, or the power could be fed back to the net. + c = _safeadd(c, p, pvpower) + consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) ++#### added for GuiMods ++ cc = _safeadd(cc, mc, pvcurrent) ++ currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) + else: + if uses_active_input: + p = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase) + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) ++#### added for GuiMods ++ currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) ++ mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + # No relevant energy meter present. Assume there is no load between the grid and the multi. + # There may be a PV inverter present though (Hub-3 setup). + if pvpower != None: + p = _safeadd(p, -pvpower) ++#### added for GuiMods ++ mc = _safeadd(mc, -pvcurrent) + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p ++#### added for GuiMods ++ newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + product_id = None + device_type_id = None +@@ -743,6 +881,7 @@ + product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') + newvalues['/Ac/%s/ProductId' % device_type] = product_id + newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id ++ + # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it + # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. + # Similarly all load are by definition on the output if this is not an ESS system. +@@ -752,26 +891,62 @@ + self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 + for phase in consumption: + c = None ++#### added for GuiMods ++ a = None + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) ++#### added for GuiMods ++ a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in vedirect_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) ++#### added for GuiMods ++ i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, calculate it + if ac_out is None: +- i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) +- u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) +- if None not in (i, u): +- ac_out = i * u ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) ++#### modified for GuiMods ++ a = _safeadd(a, i) + else: + ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) + c = _safeadd(c, ac_out) + c = _safemax(0, c) ++#### added for GuiMods ++ a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c + newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) ++#### added for GuiMods ++ newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) diff --git a/FileSets/v2.84/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v2.84 similarity index 100% rename from FileSets/v2.84/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v2.84 diff --git a/FileSets/v2.84/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v2.84.orig similarity index 100% rename from FileSets/v2.84/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v2.84.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v2.84.patch b/FileSets/PatchSource/dbus_systemcalc.py-v2.84.patch new file mode 100644 index 00000000..bd0c984f --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v2.84.patch @@ -0,0 +1,291 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.84.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.84 2023-08-31 21:32:44 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -74,6 +76,23 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L2/P': dummy, + '/Ac/Out/L3/P': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L2/I': dummy, ++ '/Ac/Out/L3/I': dummy, ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -108,7 +127,17 @@ + '/DeviceType' : dummy, + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, +- '/Ac/L3/Power': dummy}, ++ '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -118,6 +147,17 @@ + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, + '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -135,8 +175,13 @@ + '/Dc/0/Voltage': dummy, + '/Dc/0/Current': dummy, + '/Ac/Out/L1/P': dummy, ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L1/I': dummy, ++#### add for GuiMods + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -270,6 +315,49 @@ + '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, + '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -777,6 +865,13 @@ + ac_in_guess = 2 + + consumption = { "L1" : None, "L2" : None, "L3" : None } ++#### added for GuiMods ++ currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -787,32 +882,74 @@ + for phase in consumption: + p = None + pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) ++#### added for GuiMods ++ mc = None ++ pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) ++#### added for GuiMods ++ mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None ++#### added for GuiMods ++ cc = None + if uses_active_input: + ac_in = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase) + if ac_in is not None: +- c = _safeadd(c, -ac_in) ++ try: ++ c = _safeadd(c, -ac_in) ++#### added for GuiMods ++ cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ except TypeError: ++ pass ++ + # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), + # it will still be used, because there may also be a load in the same ACIn consuming + # power, or the power could be fed back to the net. + c = _safeadd(c, p, pvpower) + consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) ++#### added for GuiMods ++ cc = _safeadd(cc, mc, pvcurrent) ++ currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) + else: + if uses_active_input: + p = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase) + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) ++#### added for GuiMods ++ currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) ++ mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + # No relevant energy meter present. Assume there is no load between the grid and the multi. + # There may be a PV inverter present though (Hub-3 setup). + if pvpower != None: + p = _safeadd(p, -pvpower) ++#### added for GuiMods ++ mc = _safeadd(mc, -pvcurrent) + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p ++#### added for GuiMods ++ newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -836,11 +973,26 @@ + self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 + for phase in consumption: + c = None ++#### added for GuiMods ++ a = None + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) ++#### added for GuiMods ++ a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in vedirect_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) ++#### added for GuiMods ++ i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, calculate it + if ac_out is None: +@@ -848,14 +1000,37 @@ + u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) + if None not in (i, u): + ac_out = i * u ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) + else: + ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) + c = _safeadd(c, ac_out) + c = _safemax(0, c) ++#### added for GuiMods ++ a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c + newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) ++#### added for GuiMods ++ newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) diff --git a/FileSets/v2.89/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v2.89 similarity index 100% rename from FileSets/v2.89/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v2.89 diff --git a/FileSets/v2.89/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v2.89.orig similarity index 100% rename from FileSets/v2.89/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v2.89.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v2.89.patch b/FileSets/PatchSource/dbus_systemcalc.py-v2.89.patch new file mode 100644 index 00000000..1bf18f3e --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v2.89.patch @@ -0,0 +1,385 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.89.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.89 2023-08-31 21:32:44 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -74,6 +76,23 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L2/P': dummy, + '/Ac/Out/L3/P': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L2/I': dummy, ++ '/Ac/Out/L3/I': dummy, ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -108,7 +127,17 @@ + '/DeviceType' : dummy, + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, +- '/Ac/L3/Power': dummy}, ++ '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -118,6 +147,17 @@ + '/Ac/L1/Power': dummy, + '/Ac/L2/Power': dummy, + '/Ac/L3/Power': dummy, ++#### add for GuiMods ++ '/Ac/L1/Current': dummy, ++ '/Ac/L2/Current': dummy, ++ '/Ac/L3/Current': dummy, ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -135,8 +175,11 @@ + '/Dc/0/Voltage': dummy, + '/Dc/0/Current': dummy, + '/Ac/Out/L1/P': dummy, ++#### add for GuiMods + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -151,8 +194,12 @@ + '/Ac/In/1/L1/P': dummy, + '/Ac/In/2/L1/P': dummy, + '/Ac/Out/L1/P': dummy, ++#### add for GuiMods + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++ '/Ac/Out/L1/I': dummy, ++ '/Ac/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -230,6 +277,7 @@ + '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, + '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, + '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, ++ + '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, + '/Ac/Grid/ProductId': {'gettext': '%s'}, + '/Ac/Grid/DeviceType': {'gettext': '%s'}, +@@ -251,6 +299,9 @@ + '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, + '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, + '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, ++ '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, + '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, + '/Ac/PvOnOutput/L1/Power': {'gettext': '%.0F W'}, + '/Ac/PvOnOutput/L2/Power': {'gettext': '%.0F W'}, +@@ -286,6 +337,50 @@ + '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, + '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, ++ + } + + for m in self._modules: +@@ -783,6 +878,7 @@ + newvalues['/Ac/ActiveIn/Source'] = ac_in_source + + # ===== GRID METERS & CONSUMPTION ==== ++#### begin GuiMods replaced section - added current, voltage and frequency + grid_meter = delegates.AcInputs.instance.gridmeter + genset_meter = delegates.AcInputs.instance.gensetmeter + +@@ -799,6 +895,13 @@ + ac_in_guess = 2 + + consumption = { "L1" : None, "L2" : None, "L3" : None } ++#### added for GuiMods ++ currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -809,41 +912,109 @@ + for phase in consumption: + p = None + pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) ++#### added for GuiMods ++ mc = None ++ pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) ++#### added for GuiMods ++ mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None ++#### added for GuiMods ++ cc = None + if uses_active_input: +- if multi_path is not None and (ac_in := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: +- c = _safeadd(c, -ac_in) ++ if multi_path is not None: ++ try: ++ c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) ++#### added for GuiMods ++ cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ ++ except TypeError: ++ pass + elif non_vebus_inverter is not None and active_input in (0, 1): +- ac_in = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase)) +- if ac_in is not None: +- c = _safeadd(c, -ac_in) ++ try: ++ c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) ++#### added for GuiMods ++ cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) + ++ except TypeError: ++ pass ++ + # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), + # it will still be used, because there may also be a load in the same ACIn consuming + # power, or the power could be fed back to the net. + c = _safeadd(c, p, pvpower) + consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) ++#### added for GuiMods ++ cc = _safeadd(cc, mc, pvcurrent) ++ currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) + else: + if uses_active_input: + if multi_path is not None and ( + p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: + consumption[phase] = _safeadd(0, consumption[phase]) ++#### added for GuiMods ++ currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) ++ mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) ++#### added for GuiMods ++ mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) ++#### added for GuiMods ++ currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + + # No relevant energy meter present. Assume there is no load between the grid and the multi. + # There may be a PV inverter present though (Hub-3 setup). +- if pvpower != None: ++ try: + p = _safeadd(p, -pvpower) ++#### added for GuiMods ++ mc = _safeadd(mc, -pvcurrent) ++ except TypeError: ++ pass ++ + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p ++#### added for GuiMods ++ newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p ++#### added for GuiMods ++ newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -870,26 +1041,75 @@ + self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 + for phase in consumption: + c = None ++#### added for GuiMods ++ a = None + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) ++#### added for GuiMods ++ a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) ++#### added for GuiMods ++ i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + +- # Some models don't show power, calculate it ++ # Some models don't show power, try apparent power, ++ # else calculate it + if ac_out is None: +- i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) +- u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) +- if None not in (i, u): +- ac_out = i * u ++ ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) ++ if ac_out is None: ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) ++#### modified for GuiMods ++ a = _safeadd(a, i) + else: + ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) + c = _safeadd(c, ac_out) ++#### added for GuiMods ++ i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) ++ a = _safeadd(a, i_out) ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) ++#### added for GuiMods ++ a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c ++ newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a + newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) ++#### added for GuiMods ++ newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ ++#### end GuiMods replaced section + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) diff --git a/FileSets/v2.94/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v2.94 similarity index 100% rename from FileSets/v2.94/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v2.94 diff --git a/FileSets/v2.94/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v2.94.orig similarity index 100% rename from FileSets/v2.94/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v2.94.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v2.94.patch b/FileSets/PatchSource/dbus_systemcalc.py-v2.94.patch new file mode 100644 index 00000000..a36ac77a --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v2.94.patch @@ -0,0 +1,384 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.94.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v2.94 2023-08-31 21:32:44 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -83,6 +85,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -120,7 +136,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -133,6 +156,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -153,6 +184,9 @@ + '/Ac/Out/L1/S': dummy, + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -171,6 +205,9 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++#### add for GuiMods ++ '/Ac/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -179,6 +216,15 @@ + }, + 'com.victronenergy.alternator': { + '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { ++ '/Dc/0/Power': dummy + } + } + +@@ -335,6 +381,33 @@ + '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -354,6 +427,9 @@ + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) + ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] ++ + self._handleservicechange() + self._updatevalues() + +@@ -619,15 +695,48 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### changed for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: +- continue ++ #### continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 ++ ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p ++ ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p + + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') +@@ -791,6 +900,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -811,7 +923,12 @@ + # displayed. For now, we leave it out so that in the current + # version of Venus it does not break user's expectations. + #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power - alternator_power +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[2] ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power + alternator_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + + elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +@@ -873,6 +990,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -888,6 +1012,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -897,12 +1027,24 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): + try: + c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -920,9 +1062,23 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) + mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) +@@ -937,9 +1093,19 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p + newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -970,19 +1136,32 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) + ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) ++ + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: +- u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) +- if None not in (i, u): +- ac_out = i * u ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) + a = _safeadd(a, i) + else: +@@ -990,6 +1169,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -998,6 +1182,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) diff --git a/FileSets/v3.14/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v3.14 similarity index 100% rename from FileSets/v3.14/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v3.14 diff --git a/FileSets/v3.14/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.14.orig similarity index 100% rename from FileSets/v3.14/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v3.14.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch new file mode 100644 index 00000000..5144de45 --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch @@ -0,0 +1,402 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.14.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.14 2023-12-02 07:35:03 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -72,6 +74,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -109,7 +125,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -122,6 +145,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -143,6 +174,9 @@ + '/Ac/Out/L1/S': dummy, + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -162,6 +196,9 @@ + '/Ac/Out/L1/P': dummy, + '/Ac/Out/L1/V': dummy, + '/Ac/Out/L1/I': dummy, ++#### add for GuiMods ++ '/Ac/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -170,6 +207,15 @@ + }, + 'com.victronenergy.alternator': { + '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { ++ '/Dc/0/Power': dummy + } + } + +@@ -280,7 +326,6 @@ + '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, + '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, + '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, +- '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, + '/Dc/Pv/Power': {'gettext': '%.0F W'}, + '/Dc/Pv/Current': {'gettext': '%.1F A'}, + '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, +@@ -306,6 +351,33 @@ + '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -325,6 +397,9 @@ + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) + ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] ++ + self._handleservicechange() + self._updatevalues() + +@@ -573,15 +648,49 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### modified for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: +- continue ++ #### continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p ++ ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 ++ ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p ++ ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p + + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') +@@ -744,6 +853,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -764,7 +876,12 @@ + # displayed. For now, we leave it out so that in the current + # version of Venus it does not break user's expectations. + #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[0] ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + + elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +@@ -826,6 +943,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -841,6 +965,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -850,12 +980,24 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): + try: + c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -873,9 +1015,23 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) + mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) +@@ -890,9 +1046,18 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: +- newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p +- newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++ newvalues['/Ac/ActiveIn/%s/Power' % phase] = p ++ newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -923,19 +1088,31 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: +- u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) +- if None not in (i, u): +- ac_out = i * u ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) + a = _safeadd(a, i) + else: +@@ -943,6 +1120,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -951,6 +1133,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) +@@ -962,7 +1160,7 @@ + with self._dbusservice as sss: + for path in self._summeditems.keys(): + # Why the None? Because we want to invalidate things we don't have anymore. +- sss[path] = newvalues.get(path, None) ++ sss[path] = newvalues.get(path, None) + + def _handleservicechange(self): + # Update the available battery monitor services, used to populate the dropdown in the settings. diff --git a/FileSets/v3.40~10/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.orig similarity index 100% rename from FileSets/v3.40~10/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.patch new file mode 100644 index 00000000..943be143 --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.patch @@ -0,0 +1,392 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~10 2024-04-23 14:45:34 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -72,6 +74,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -109,7 +125,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -122,6 +145,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -151,6 +182,9 @@ + '/Ac/Out/L3/S': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -184,6 +218,9 @@ + '/Ac/Out/L3/P': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -192,6 +229,15 @@ + }, + 'com.victronenergy.alternator': { + '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { ++ '/Dc/0/Power': dummy + } + } + +@@ -348,6 +394,8 @@ + '/Dc/Charger/Power': {'gettext': '%.0F %%'}, + '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, + '/Dc/Alternator/Power': {'gettext': '%.0F W'}, ++ '/Dc/Vebus/Current': {'gettext': '%.1F A'}, ++ '/Dc/Vebus/Power': {'gettext': '%.0F W'}, + '/Dc/System/Power': {'gettext': '%.0F W'}, + '/Dc/System/MeasurementType': {'gettext': '%d'}, + '/Ac/ActiveIn/Source': {'gettext': '%s'}, +@@ -365,7 +413,34 @@ + '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, + '/Pv/Power/Max': {'gettext': '%d'}, + '/Dc/Input/Power/Max': {'gettext': '%d'}, +- '/Dc/System/Power/Max': {'gettext': '%d'} ++ '/Dc/System/Power/Max': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -384,6 +459,9 @@ + self._changed = True + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) ++ ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] + + self._handleservicechange() + self._updatevalues() +@@ -633,16 +711,50 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### modified for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: + continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p + ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 ++ ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p ++ ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p ++ + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') + charger_batteryvoltage = None +@@ -804,6 +916,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -824,7 +939,12 @@ + # displayed. For now, we leave it out so that in the current + # version of Venus it does not break user's expectations. + #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[0] ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + + elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +@@ -871,6 +991,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -886,6 +1013,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -895,6 +1028,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): +@@ -902,6 +1041,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -919,12 +1064,26 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + for i in non_vebus_inverters: + p = _safeadd(p, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) + mc = _safeadd(mc, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) +@@ -939,9 +1098,18 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p + newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -972,16 +1140,28 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: ++#### modified for GuiMods + u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) + if None not in (i, u): + ac_out = i * u +@@ -992,6 +1172,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -1000,6 +1185,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) diff --git a/FileSets/v3.40~15/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15 similarity index 100% rename from FileSets/v3.40~15/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~15 diff --git a/FileSets/v3.40~15/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.orig similarity index 100% rename from FileSets/v3.40~15/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.patch new file mode 100644 index 00000000..ef9781ab --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.patch @@ -0,0 +1,569 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~15 2024-05-17 06:54:36 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -72,6 +74,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -109,7 +125,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -122,6 +145,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -151,6 +182,9 @@ + '/Ac/Out/L3/S': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -184,6 +218,9 @@ + '/Ac/Out/L3/P': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -191,6 +228,15 @@ + '/Dc/0/Power': dummy + }, + 'com.victronenergy.alternator': { ++ '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { + '/Dc/0/Power': dummy + } + } +@@ -232,24 +278,39 @@ + self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, + deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) + ++ # Used to store AC output maximum values for the scenarios: ++ # No AC input connected ++ # Ac input 1 connected ++ # Ac input 2 connected ++ self._acMaxima = { ++ 'NoAcIn': 0, ++ 'AcIn1': 0, ++ 'AcIn2': 0 ++ } ++ ++ self._minMaxPaths = { ++ '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], ++ '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], ++ '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], ++ '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], ++ '/Dc/Input/Power/Max': [float(0), 0, float("inf")], ++ '/Dc/System/Power/Max': [float(0), 0, float("inf")], ++ '/Pv/Power/Max': [float(0), 0, float("inf")] ++ } ++ ++ for p in self._acMaxima.keys(): ++ self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] ++ + # Connect to localsettings + supported_settings = { + 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], + 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], + 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], +- 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], +- 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], +- 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], +- 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], +- 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], +- 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], +- 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], +- 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], +- 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], +- 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], +- 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], +- } ++ 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} + ++ for p, s in self._minMaxPaths.items(): ++ supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] ++ + for m in self._modules: + for setting in m.get_settings(): + supported_settings[setting[0]] = list(setting[1:]) +@@ -333,6 +394,8 @@ + '/Dc/Charger/Power': {'gettext': '%.0F %%'}, + '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, + '/Dc/Alternator/Power': {'gettext': '%.0F W'}, ++ '/Dc/Vebus/Current': {'gettext': '%.1F A'}, ++ '/Dc/Vebus/Power': {'gettext': '%.0F W'}, + '/Dc/System/Power': {'gettext': '%.0F W'}, + '/Dc/System/MeasurementType': {'gettext': '%d'}, + '/Ac/ActiveIn/Source': {'gettext': '%s'}, +@@ -343,6 +406,41 @@ + '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++ '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, ++ '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, ++ '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, ++ '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, ++ '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, ++ '/Pv/Power/Max': {'gettext': '%d'}, ++ '/Dc/Input/Power/Max': {'gettext': '%d'}, ++ '/Dc/System/Power/Max': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -362,6 +460,9 @@ + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) + ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] ++ + self._handleservicechange() + self._updatevalues() + +@@ -611,16 +712,50 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### modified for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: + continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p ++ ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 ++ ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p + ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p ++ + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') + charger_batteryvoltage = None +@@ -782,6 +917,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -802,7 +940,12 @@ + # displayed. For now, we leave it out so that in the current + # version of Venus it does not break user's expectations. + #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[0] ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + + elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +@@ -849,6 +992,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -864,6 +1014,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -873,6 +1029,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): +@@ -880,6 +1042,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -897,12 +1065,26 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + for i in non_vebus_inverters: + p = _safeadd(p, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) + mc = _safeadd(mc, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) +@@ -917,9 +1099,18 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p + newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -950,16 +1141,28 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: ++#### modified for GuiMods + u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) + if None not in (i, u): + ac_out = i * u +@@ -970,6 +1173,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -978,6 +1186,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) +@@ -986,29 +1210,29 @@ + m.update_values(newvalues) + + # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== +- if (self._settings['gaugeautomax']): +- # min/max values are stored and updated in localsettings +- # values are stored under /Settings/Gui/Briefview +- # /Settings/Gui/Gauges/AutoMax: +- # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings +- # 0-> Manual: Gauge limits are entered manually by the user +- # The gui pulls the gauge limits from localsettings and provides +- # a means for the user to set them if Automax is off. ++ # min/max values are stored in localsettings and synched once in a while. ++ # values are stored under /Settings/Gui/Briefview ++ # /Settings/Gui/Gauges/AutoMax: ++ # 1-> Automatic: Maxima are updated automatically and synched to localsettings ++ # 0-> Manual: Maxima are pulled from localsettings. ++ # min/max computations are done here because the _updatevalues method abstracts them ++ # away from the delegates. + +- # AC output +- # This maximum is maintained for 3 situations: +- # 1: AC input 1 is connected +- # 2: AC input 2 is connected +- # 3: No AC input is connected +- # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. +- # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. +- activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ +- 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ +- 'noacin' ++ # AC output ++ # This maximum is maintained for 3 situations: ++ # 1: AC input 1 is connected ++ # 2: AC input 2 is connected ++ # 3: No AC input is connected ++ # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. ++ # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. ++ activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ ++ 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ ++ 'NoAcIn' + +- # Quattro has 2 AC inputs which cannot be active simultaneously. +- # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. +- activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None ++ # Quattro has 2 AC inputs which cannot be active simultaneously. ++ # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. ++ if (self._settings['gaugeautomax']): ++ activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None + + # AC input + # Minimum values occur when feeding back to the grid. +@@ -1016,34 +1240,36 @@ + # Update correct '/Ac/In/..' based on the current active input. + # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. + if(activeInNr != None): +- self._settings['acin%smin' % activeInNr] = min(0, +- self._settings['acin%smin' % activeInNr] or float("inf"), ++ newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, ++ self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), + newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), + newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), + newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) + +- self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, ++ newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, + newvalues.get('/Ac/ActiveIn/L1/Current') or 0, + newvalues.get('/Ac/ActiveIn/L2/Current') or 0, + newvalues.get('/Ac/ActiveIn/L3/Current') or 0) + +- self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], ++ self._acMaxima[activeIn] = max(self._acMaxima[activeIn], + newvalues.get('/Ac/Consumption/L1/Current') or 0, + newvalues.get('/Ac/Consumption/L2/Current') or 0, + newvalues.get('/Ac/Consumption/L3/Current') or 0) + ++ newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] ++ + # DC input +- self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, ++ newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, + sum([newvalues.get('/Dc/Charger/Power') or 0, + newvalues.get('/Dc/FuelCell/Power') or 0, + newvalues.get('/Dc/Alternator/Power') or 0])) + + # DC output +- self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, ++ newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, + newvalues.get('/Dc/System/Power') or 0) + + # PV power +- self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, ++ newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, + _safeadd(newvalues.get('/Dc/Pv/Power') or 0, + self._dbusservice['/Ac/PvOnGrid/L1/Power'], + self._dbusservice['/Ac/PvOnGrid/L2/Power'], +@@ -1055,6 +1281,23 @@ + self._dbusservice['/Ac/PvOnOutput/L2/Power'], + self._dbusservice['/Ac/PvOnOutput/L3/Power'])) + ++ # Sync max values to localsettings (once each second) ++ for p in self._minMaxPaths.keys(): ++ if (p in newvalues and newvalues[p] != self._settings[p]): ++ self._settings[p] = newvalues[p] ++ ++ # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. ++ if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): ++ self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] ++ ++ # Manual mode: relay min/max settings from localsettings to newvalues ++ # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated ++ else: ++ for p in self._minMaxPaths.keys(): ++ newvalues[p] = self._settings[p] ++ ++ newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] ++ + # ==== UPDATE DBUS ITEMS ==== + with self._dbusservice as sss: + for path in self._summeditems.keys(): diff --git a/FileSets/v3.40~2/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2 similarity index 100% rename from FileSets/v3.40~2/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~2 diff --git a/FileSets/v3.40~2/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.orig similarity index 100% rename from FileSets/v3.40~2/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.patch new file mode 100644 index 00000000..341f40ba --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.patch @@ -0,0 +1,413 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2.orig 2024-07-08 07:47:23 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~2 2024-04-04 21:38:40 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -72,6 +74,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -109,7 +125,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -122,6 +145,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -151,6 +182,9 @@ + '/Ac/Out/L3/S': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -184,6 +218,9 @@ + '/Ac/Out/L3/P': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { +@@ -192,6 +229,15 @@ + }, + 'com.victronenergy.alternator': { + '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { ++ '/Dc/0/Power': dummy + } + } + +@@ -303,7 +349,6 @@ + '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, + '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, + '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, +- '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, + '/Dc/Pv/Power': {'gettext': '%.0F W'}, + '/Dc/Pv/Current': {'gettext': '%.1F A'}, + '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, +@@ -329,6 +374,33 @@ + '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -348,6 +420,9 @@ + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) + ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] ++ + self._handleservicechange() + self._updatevalues() + +@@ -596,15 +671,49 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### modified for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: +- continue ++ #### continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p ++ ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 ++ ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p ++ ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p + + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') +@@ -767,6 +876,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -787,7 +899,12 @@ + # displayed. For now, we leave it out so that in the current + # version of Venus it does not break user's expectations. + #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[0] ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + + elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +@@ -849,6 +966,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -864,6 +988,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -873,6 +1003,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): +@@ -880,6 +1016,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -897,16 +1039,30 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + for i in non_vebus_inverters: + p = _safeadd(p, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) + mc = _safeadd(mc, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) +- if p is not None: +- consumption[phase] = _safeadd(0, consumption[phase]) +- currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) + ++ if p is not None: ++ consumption[phase] = _safeadd(0, consumption[phase]) ++ currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) ++ + # No relevant energy meter present. Assume there is no load between the grid and the multi. + # There may be a PV inverter present though (Hub-3 setup). + try: +@@ -917,9 +1073,18 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: +- newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p +- newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++ newvalues['/Ac/ActiveIn/%s/Power' % phase] = p ++ newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -950,19 +1115,31 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: +- u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) +- if None not in (i, u): +- ac_out = i * u ++#### modified for GuiMods ++ # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if None not in (i, voltageOut[phase]): ++ ac_out = i * voltageOut[phase] + c = _safeadd(c, ac_out) + a = _safeadd(a, i) + else: +@@ -970,6 +1147,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -978,6 +1160,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) +@@ -989,7 +1187,7 @@ + with self._dbusservice as sss: + for path in self._summeditems.keys(): + # Why the None? Because we want to invalidate things we don't have anymore. +- sss[path] = newvalues.get(path, None) ++ sss[path] = newvalues.get(path, None) + + def _handleservicechange(self): + # Update the available battery monitor services, used to populate the dropdown in the settings. diff --git a/FileSets/v3.40~35/dbus_systemcalc.py b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35 similarity index 100% rename from FileSets/v3.40~35/dbus_systemcalc.py rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~35 diff --git a/FileSets/v3.40~35/dbus_systemcalc.py.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.orig similarity index 100% rename from FileSets/v3.40~35/dbus_systemcalc.py.orig rename to FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.orig diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.patch new file mode 100644 index 00000000..5426930f --- /dev/null +++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.patch @@ -0,0 +1,574 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35.orig 2024-07-08 07:47:22 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.40~35 2024-07-08 07:46:54 +@@ -1,6 +1,8 @@ + #!/usr/bin/python3 -u + # -*- coding: utf-8 -*- + ++#### modified for GuiMods ++ + from dbus.mainloop.glib import DBusGMainLoop + import dbus + import argparse +@@ -72,6 +74,20 @@ + '/Ac/Out/L1/I': dummy, + '/Ac/Out/L2/I': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/V': dummy, ++ '/Ac/Out/L2/V': dummy, ++ '/Ac/Out/L3/V': dummy, ++ '/Ac/Out/L1/F': dummy, ++ '/Ac/Out/L2/F': dummy, ++ '/Ac/Out/L3/F': dummy, ++ '/Ac/ActiveIn/L1/V': dummy, ++ '/Ac/ActiveIn/L2/V': dummy, ++ '/Ac/ActiveIn/L3/V': dummy, ++ '/Ac/ActiveIn/L1/F': dummy, ++ '/Ac/ActiveIn/L2/F': dummy, ++ '/Ac/ActiveIn/L3/F': dummy, ++ + '/Connected': dummy, + '/ProductId': dummy, + '/ProductName': dummy, +@@ -109,7 +125,14 @@ + '/Ac/L3/Power': dummy, + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, +- '/Ac/L3/Current': dummy}, ++ '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy}, + 'com.victronenergy.genset' : { + '/Connected': dummy, + '/ProductName': dummy, +@@ -122,6 +145,14 @@ + '/Ac/L1/Current': dummy, + '/Ac/L2/Current': dummy, + '/Ac/L3/Current': dummy, ++#### add for GuiMods ++ '/Ac/L1/Voltage': dummy, ++ '/Ac/L2/Voltage': dummy, ++ '/Ac/L3/Voltage': dummy, ++ '/Ac/L1/Frequency': dummy, ++ '/Ac/L2/Frequency': dummy, ++ '/Ac/L3/Frequency': dummy, ++ + '/StarterVoltage': dummy}, + 'com.victronenergy.settings' : { + '/Settings/SystemSetup/AcInput1' : dummy, +@@ -151,6 +182,9 @@ + '/Ac/Out/L3/S': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.multi': { +@@ -184,15 +218,26 @@ + '/Ac/Out/L3/P': dummy, + '/Ac/Out/L3/V': dummy, + '/Ac/Out/L3/I': dummy, ++#### add for GuiMods ++ '/Ac/Out/L1/F': dummy, ++ + '/Yield/Power': dummy, + '/Soc': dummy}, + 'com.victronenergy.dcsystem': { + '/Dc/0/Voltage': dummy, +- '/Dc/0/Power': dummy, +- '/Dc/0/Current': dummy, ++ '/Dc/0/Power': dummy + }, + 'com.victronenergy.alternator': { + '/Dc/0/Power': dummy ++ }, ++#### added for GuiMods ++ 'com.victronenergy.dcsource': { ++ '/Dc/0/Power': dummy, ++ '/Settings/MonitorMode': dummy ++ }, ++ 'com.victronenergy.motordrive': ++ { ++ '/Dc/0/Power': dummy + } + } + +@@ -233,24 +278,39 @@ + self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, + deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) + ++ # Used to store AC output maximum values for the scenarios: ++ # No AC input connected ++ # Ac input 1 connected ++ # Ac input 2 connected ++ self._acMaxima = { ++ 'NoAcIn': 0, ++ 'AcIn1': 0, ++ 'AcIn2': 0 ++ } ++ ++ self._minMaxPaths = { ++ '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], ++ '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], ++ '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], ++ '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], ++ '/Dc/Input/Power/Max': [float(0), 0, float("inf")], ++ '/Dc/System/Power/Max': [float(0), 0, float("inf")], ++ '/Pv/Power/Max': [float(0), 0, float("inf")] ++ } ++ ++ for p in self._acMaxima.keys(): ++ self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] ++ + # Connect to localsettings + supported_settings = { + 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], + 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], + 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], +- 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], +- 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], +- 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], +- 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], +- 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], +- 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], +- 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], +- 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], +- 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], +- 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], +- 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], +- } ++ 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} + ++ for p, s in self._minMaxPaths.items(): ++ supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] ++ + for m in self._modules: + for setting in m.get_settings(): + supported_settings[setting[0]] = list(setting[1:]) +@@ -334,6 +394,8 @@ + '/Dc/Charger/Power': {'gettext': '%.0F %%'}, + '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, + '/Dc/Alternator/Power': {'gettext': '%.0F W'}, ++ '/Dc/Vebus/Current': {'gettext': '%.1F A'}, ++ '/Dc/Vebus/Power': {'gettext': '%.0F W'}, + '/Dc/System/Power': {'gettext': '%.0F W'}, + '/Dc/System/Current': {'gettext': '%.1F A'}, + '/Dc/System/MeasurementType': {'gettext': '%d'}, +@@ -345,6 +407,41 @@ + '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, + '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, ++ '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, ++ '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, ++ '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, ++ '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, ++ '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, ++ '/Pv/Power/Max': {'gettext': '%d'}, ++ '/Dc/Input/Power/Max': {'gettext': '%d'}, ++ '/Dc/System/Power/Max': {'gettext': '%d'}, ++#### added for GuiMods ++ '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, ++ '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, ++ '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, ++ '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, ++ '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, + } + + for m in self._modules: +@@ -364,6 +461,9 @@ + for service, instance in self._dbusmonitor.get_service_list().items(): + self._device_added(service, instance, do_service_change=False) + ++#### added for GuiMods ++ self.dcSystemPower = [0, 0, 0] ++ + self._handleservicechange() + self._updatevalues() + +@@ -613,16 +713,50 @@ + # ==== ALTERNATOR ==== + alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') + for alternator in alternators: ++#### modified for GuiMods ++ # some alternators do not provide a valid power value if not running ++ # or below a minimum power/current ++ # so fill in a zero power so that the systemcalc power becomes valid + # Assume the battery connected to output 0 is the main battery + p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') + if p is None: + continue ++ p = 0 + + if '/Dc/Alternator/Power' not in newvalues: + newvalues['/Dc/Alternator/Power'] = p + else: + newvalues['/Dc/Alternator/Power'] += p ++ ++ ++#### added for GuiMods ++ # ==== MOTOR DRIVE ==== ++ motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') ++ for motordrive in motordrives: ++ p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') ++ if p is None: ++ p = 0 + ++ if '/Dc/MotorDrive/Power' not in newvalues: ++ newvalues['/Dc/MotorDrive/Power'] = p ++ else: ++ newvalues['/Dc/MotorDrive/Power'] += p ++ ++#### added for GuiMods ++ # ==== DC SOURCES ==== ++ dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') ++ for dcSource in dcSources: ++ monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') ++ # ==== WIND GENERATOR ==== ++ if monitorMode == -8: ++ p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') ++ if p is None: ++ continue ++ if '/Dc/WindGenerator/Power' not in newvalues: ++ newvalues['/Dc/WindGenerator/Power'] = p ++ else: ++ newvalues['/Dc/WindGenerator/Power'] += p ++ + # ==== CHARGERS ==== + chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') + charger_batteryvoltage = None +@@ -787,6 +921,9 @@ + charger_power = newvalues.get('/Dc/Charger/Power', 0) + fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) + alternator_power = newvalues.get('/Dc/Alternator/Power', 0) ++#### added for GuiMods ++ windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) ++ motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) + + # If there are VE.Direct inverters, remove their power from the + # DC estimate. This is done using the AC value when the DC +@@ -802,7 +939,14 @@ + i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( + i, '/Ac/Out/L1/I', 0) + newvalues['/Dc/System/MeasurementType'] = 0 # estimated +- newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power ++#### changed for GuiMods ++ # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value ++ self.dcSystemPower[2] = self.dcSystemPower[1] ++ self.dcSystemPower[1] = self.dcSystemPower[0] ++#### changed for GuiMods - include wind and motor drive ++ #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power ++ self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power ++ newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 + try: + newvalues['/Dc/System/Current'] = \ + newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] +@@ -859,6 +1003,13 @@ + + consumption = { "L1" : None, "L2" : None, "L3" : None } + currentconsumption = { "L1" : None, "L2" : None, "L3" : None } ++ ++#### added for GuiMods ++ voltageIn = { "L1" : None, "L2" : None, "L3" : None } ++ voltageOut = { "L1" : None, "L2" : None, "L3" : None } ++ frequencyIn = None ++ frequencyOut = None ++ + for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): + # If a grid meter is present we use values from it. If not, we look at the multi. If it has + # AcIn1 or AcIn2 connected to the grid, we use those values. +@@ -874,6 +1025,12 @@ + if em is not None: + p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) + mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) ++ + # Compute consumption between energy meter and multi (meter power - multi AC in) and + # add an optional PV inverter on input to the mix. + c = None +@@ -883,6 +1040,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) + cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) ++ + except TypeError: + pass + elif non_vebus_inverter is not None and active_input in (0, 1): +@@ -890,6 +1053,12 @@ + try: + c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) + cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) ++ + except TypeError: + pass + +@@ -907,12 +1076,26 @@ + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) + mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) ++ if frequencyIn == None: ++ freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) ++ if freq != None: ++ frequencyIn = freq ++ + elif non_vebus_inverter is not None and active_input in (0, 1): + for i in non_vebus_inverters: + p = _safeadd(p, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) + mc = _safeadd(mc, + self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) ++#### added for GuiMods ++ if voltageIn[phase] == None: ++ voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) ++ if frequencyIn == None: ++ frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) ++ + if p is not None: + consumption[phase] = _safeadd(0, consumption[phase]) + currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) +@@ -927,9 +1110,18 @@ + + newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p + newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] ++ newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn ++ + if ac_in_guess in _types: + newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p + newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc ++#### added for GuiMods ++ if p != None: ++ newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] ++ newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn + + self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) + self._compute_number_of_phases('/Ac/ActiveIn', newvalues) +@@ -960,16 +1152,28 @@ + if use_ac_out: + c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) + a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) ++ if frequencyOut == None: ++ frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) ++ + if multi_path is None: + for inv in non_vebus_inverters: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) + i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) + + # Some models don't show power, try apparent power, + # else calculate it + if ac_out is None: + ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) + if ac_out is None: ++#### modified for GuiMods + u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) + if None not in (i, u): + ac_out = i * u +@@ -980,6 +1184,11 @@ + c = _safeadd(c, ac_out) + i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) + a = _safeadd(a, i_out) ++#### added for GuiMods ++ if voltageOut[phase] == None: ++ voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) ++ if frequencyOut == None: ++ frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) + c = _safemax(0, c) + a = _safemax(0, a) + newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c +@@ -988,6 +1197,22 @@ + newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] + newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) + newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) ++#### added for GuiMods ++ newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] ++ newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] ++ if voltageOut[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] ++ elif voltageIn[phase] != None: ++ newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] ++ if frequencyIn != None: ++ newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn ++ if frequencyOut != None: ++ newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut ++ if frequencyOut != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyOut ++ elif frequencyIn != None: ++ newvalues['/Ac/Consumption/Frequency'] = frequencyIn ++ + self._compute_number_of_phases('/Ac/Consumption', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) + self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) +@@ -996,29 +1221,29 @@ + m.update_values(newvalues) + + # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== +- if (self._settings['gaugeautomax']): +- # min/max values are stored and updated in localsettings +- # values are stored under /Settings/Gui/Briefview +- # /Settings/Gui/Gauges/AutoMax: +- # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings +- # 0-> Manual: Gauge limits are entered manually by the user +- # The gui pulls the gauge limits from localsettings and provides +- # a means for the user to set them if Automax is off. ++ # min/max values are stored in localsettings and synched once in a while. ++ # values are stored under /Settings/Gui/Briefview ++ # /Settings/Gui/Gauges/AutoMax: ++ # 1-> Automatic: Maxima are updated automatically and synched to localsettings ++ # 0-> Manual: Maxima are pulled from localsettings. ++ # min/max computations are done here because the _updatevalues method abstracts them ++ # away from the delegates. + +- # AC output +- # This maximum is maintained for 3 situations: +- # 1: AC input 1 is connected +- # 2: AC input 2 is connected +- # 3: No AC input is connected +- # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. +- # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. +- activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ +- 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ +- 'noacin' ++ # AC output ++ # This maximum is maintained for 3 situations: ++ # 1: AC input 1 is connected ++ # 2: AC input 2 is connected ++ # 3: No AC input is connected ++ # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. ++ # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. ++ activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ ++ 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ ++ 'NoAcIn' + +- # Quattro has 2 AC inputs which cannot be active simultaneously. +- # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. +- activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None ++ # Quattro has 2 AC inputs which cannot be active simultaneously. ++ # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. ++ if (self._settings['gaugeautomax']): ++ activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None + + # AC input + # Minimum values occur when feeding back to the grid. +@@ -1026,34 +1251,36 @@ + # Update correct '/Ac/In/..' based on the current active input. + # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. + if(activeInNr != None): +- self._settings['acin%smin' % activeInNr] = min(0, +- self._settings['acin%smin' % activeInNr] or float("inf"), ++ newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, ++ self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), + newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), + newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), + newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) + +- self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, ++ newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, + newvalues.get('/Ac/ActiveIn/L1/Current') or 0, + newvalues.get('/Ac/ActiveIn/L2/Current') or 0, + newvalues.get('/Ac/ActiveIn/L3/Current') or 0) + +- self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], ++ self._acMaxima[activeIn] = max(self._acMaxima[activeIn], + newvalues.get('/Ac/Consumption/L1/Current') or 0, + newvalues.get('/Ac/Consumption/L2/Current') or 0, + newvalues.get('/Ac/Consumption/L3/Current') or 0) + ++ newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] ++ + # DC input +- self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, ++ newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, + sum([newvalues.get('/Dc/Charger/Power') or 0, + newvalues.get('/Dc/FuelCell/Power') or 0, + newvalues.get('/Dc/Alternator/Power') or 0])) + + # DC output +- self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, ++ newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, + newvalues.get('/Dc/System/Power') or 0) + + # PV power +- self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, ++ newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, + _safeadd(newvalues.get('/Dc/Pv/Power') or 0, + self._dbusservice['/Ac/PvOnGrid/L1/Power'], + self._dbusservice['/Ac/PvOnGrid/L2/Power'], +@@ -1065,6 +1292,23 @@ + self._dbusservice['/Ac/PvOnOutput/L2/Power'], + self._dbusservice['/Ac/PvOnOutput/L3/Power'])) + ++ # Sync max values to localsettings (once each second) ++ for p in self._minMaxPaths.keys(): ++ if (p in newvalues and newvalues[p] != self._settings[p]): ++ self._settings[p] = newvalues[p] ++ ++ # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. ++ if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): ++ self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] ++ ++ # Manual mode: relay min/max settings from localsettings to newvalues ++ # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated ++ else: ++ for p in self._minMaxPaths.keys(): ++ newvalues[p] = self._settings[p] ++ ++ newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] ++ + # ==== UPDATE DBUS ITEMS ==== + with self._dbusservice as sss: + for path in self._summeditems.keys(): diff --git a/FileSets/v2.94/main.qml b/FileSets/PatchSource/main.qml-v2.94 similarity index 100% rename from FileSets/v2.94/main.qml rename to FileSets/PatchSource/main.qml-v2.94 diff --git a/FileSets/v2.94/main.qml.orig b/FileSets/PatchSource/main.qml-v2.94.orig similarity index 100% rename from FileSets/v2.94/main.qml.orig rename to FileSets/PatchSource/main.qml-v2.94.orig diff --git a/FileSets/PatchSource/main.qml-v2.94.patch b/FileSets/PatchSource/main.qml-v2.94.patch new file mode 100644 index 00000000..ca2a6bf5 --- /dev/null +++ b/FileSets/PatchSource/main.qml-v2.94.patch @@ -0,0 +1,470 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v2.94.orig 2024-07-08 09:16:57 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v2.94 2023-05-19 16:35:06 +@@ -1,3 +1,6 @@ ++//////// Modified to hide the OverviewTiles page ++//////// Modified to substitute flow overview pages ++ + import QtQuick 1.1 + + import Qt.labs.components.native 1.0 +@@ -16,48 +19,108 @@ + property bool completed: false + property bool showAlert: NotificationCenter.alert + property bool alarm: NotificationCenter.alarm +- property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid ++//////// added for GuiMods flow pages ++ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid + property string bindPrefix: "com.victronenergy.settings" + + property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") + property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; + property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; + +- ++//////// modified for GuiMods pages + property string hubOverviewType: theSystem.systemType.valid ? +- withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" ++ withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" ++ property string currentHubOverview: "OverviewHub.qml" ++ property string currentMobileOverview: "" ++ property string currentGeneratorOverview: "" + + // Keep track of the current view (menu/overview) to show as default next time the + // CCGX is restarted + onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) + +- // Add the correct OverviewHub page +- onHubOverviewTypeChanged: { +- switch(hubOverviewType){ +- case "Hub": +- case "Hub-1": +- case "Hub-2": +- case "Hub-3": +- replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); +- break; +- case "Hub-4": +- case "ESS": +- replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); +- break; +- default: +- break; ++ // Add the correct OverviewGridParallelEnhanced page ++//////// modified for OverviewHubEnhanced page ++ onHubOverviewTypeChanged: selectHubOverview () ++ ++ VBusItem ++ { ++ id: guiModsFlowOverview ++ bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" ++ onValueChanged: selectHubOverview () ++ } ++ ++////// GuiMods — DarkMode ++ property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } ++ property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 ++ ++////// GuiMods — DarkMode ++ Rectangle { ++ anchors ++ { ++ fill: parent + } +- // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) +- // by adding a return statement +- return ++ color: !darkMode ? "transparent" : "#202020" ++ z: -1 + } + ++ // base a new hub selection on the hub type and the enhanced flow overview flag ++ function selectHubOverview () ++ { ++ var newHubOverview = currentHubOverview ++ // Victron stock overviews with automatic selection ++ if (guiModsFlowOverview.value == 0) ++ { ++ switch(hubOverviewType){ ++ case "Hub": ++ case "Hub-1": ++ case "Hub-2": ++ case "Hub-3": ++ case "unknown": ++ newHubOverview = "OverviewHub.qml" ++ break; ++ case "Hub-4": ++ case "ESS": ++ newHubOverview = "OverviewGridParallel.qml" ++ break; ++ default: ++ break; ++ } ++ } ++ // Gui Mods simple flow ++ else if (guiModsFlowOverview.value === 1) ++ { ++ newHubOverview = "OverviewHubEnhanced.qml" ++ } ++ // Gui Mods complex flow (AC coupled or DC coupled) ++ else ++ { ++ newHubOverview = "OverviewFlowComplex.qml" ++ } ++ ++ if (newHubOverview != currentHubOverview) ++ { ++ replaceOverview(currentHubOverview, newHubOverview); ++ currentHubOverview = newHubOverview ++ } ++ ++ // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) ++ // by adding a return statement ++ return ++ } ++ + VBusItem { + id: generatorOverview + bind: "com.victronenergy.settings/Settings/Relay/Function" +- onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) ++ onValueChanged: selectGeneratorOverview () + } + ++ VBusItem ++ { ++ id: generatorOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" ++ onValueChanged: selectGeneratorOverview () ++ } ++ + VBusItem { + id: fischerPandaGenOverview + bind: "com.victronenergy.generator.startstop1/AutoStartEnabled" +@@ -69,22 +132,103 @@ + } + } + } +- +- VBusItem { +- id: mobileOverview +- bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" +- onValueChanged:{ +- extraOverview("OverviewMobile.qml", value === 1) +- } ++ function selectGeneratorOverview () ++ { ++ var newGeneratorOverview ++ if (generatorOverview.value === 1) ++ { ++ if (generatorOverviewEnhanced.value === 1) ++ newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" ++ else ++ newGeneratorOverview = "OverviewGeneratorRelay.qml" ++ if (currentGeneratorOverview === "") ++ extraOverview (newGeneratorOverview, true) ++ else ++ replaceOverview (currentGeneratorOverview, newGeneratorOverview) ++ currentGeneratorOverview = newGeneratorOverview ++ } ++ else ++ { ++ // hide existing generator overview if any ++ if (currentGeneratorOverview != "") ++ { ++ extraOverview (currentGeneratorOverview, false) ++ currentGeneratorOverview = "" ++ } ++ } + } +- VBusItem { +- id: tanksOverview +- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" +- onValueChanged:{ +- extraOverview("OverviewTanks.qml", value === 1) +- } +- } + ++//////// handle OverviewMobileEnhanced page ++ VBusItem ++ { ++ id: mobileOverview ++ bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ VBusItem ++ { ++ id: mobileOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ ++ // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags ++ function selectMobileOverview () ++ { ++ var newMobileOverview ++ if (mobileOverview.value === 1) ++ { ++ if (mobileOverviewEnhanced.value === 1) ++ newMobileOverview = "OverviewMobileEnhanced.qml" ++ else ++ newMobileOverview = "OverviewMobile.qml" ++ if (currentMobileOverview === "") ++ extraOverview (newMobileOverview, true) ++ else ++ replaceOverview (currentMobileOverview, newMobileOverview) ++ currentMobileOverview = newMobileOverview ++ } ++ else ++ { ++ // hide existing mobile overview if any ++ if (currentMobileOverview != "") ++ { ++ extraOverview (currentMobileOverview, false) ++ currentMobileOverview = "" ++ } ++ } ++ } ++ ++//////// show/hide the OverviewTiles page ++ VBusItem ++ { ++ id: showOverviewTiles ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" ++ onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) ++ } ++ ++//////// show/hide the OverviewRelays page ++ VBusItem { ++ id: showOverviewRelays ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" ++ onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) ++ } ++ ++//////// show/hide the Overview Tanks/Temps/Digital Inputs page ++ VBusItem { ++ id: showOverviewTanksTemps ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" ++ onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) ++ } ++ ++ VBusItem { ++ id: tanksOverview ++ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" ++ onValueChanged:{ ++ extraOverview("OverviewTanks.qml", value === 1) ++ } ++ } ++ + VBusItem { + id: startWithMenu + bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" +@@ -150,76 +294,111 @@ + id: mbTools + height: parent.height + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- anchors.left: mbTools.left +- height: mbTools.height +- width: 200 ++//// GuiMods - DarkMode ++ Row ++ { ++ spacing: 0 ++ anchors.fill: parent ++ Item { ++ id: pagesItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 170 + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.leftAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.leftAction(true) ++ } + } ++ ++ Row { ++ anchors.verticalCenter: parent.verticalCenter ++ anchors.horizontalCenter: parent.horizontalCenter ++ ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ } ++ ++ Text { ++ anchors.verticalCenter: parent.verticalCenter ++ text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } ++ } + } + +- Row { +- anchors.centerIn: parent ++ Item { ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width + +- MbIcon { +- anchors.verticalCenter: parent.verticalCenter +- iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ MouseArea ++ { ++ anchors.fill: parent ++ onClicked: ++ { ++ if (darkModeItem.valid) ++ darkModeItem.setValue (! darkMode) ++ } + } + +- Text { +- anchors.verticalCenter: parent.verticalCenter +- text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ Text ++ { ++ anchors.fill: parent ++ horizontalAlignment: Text.AlignHCenter ++ text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) + color: "white" + font.bold: true +- font.pixelSize: 16 ++ font.pixelSize: 12 ++ visible: darkModeItem.valid + } + } +- } +- +- MbIcon { +- id: centerScrollIndicator +- +- anchors { +- horizontalCenter: parent.horizontalCenter +- verticalCenter: mbTools.verticalCenter ++ Item ++ { ++ id: centerScrollIndicator ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 20 ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" ++ } + } +- iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" +- } + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- height: mbTools.height +- anchors.right: mbTools.right +- width: 200 ++ Item { ++ id: menusItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: pagesItem.width + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.rightAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.rightAction(true) ++ } + } +- } + +- Row { +- anchors.centerIn: parent ++ Row { ++ anchors.centerIn: parent + +- MbIcon { +- iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" +- anchors.verticalCenter: parent.verticalCenter +- } ++ MbIcon { ++ iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" ++ anchors.verticalCenter: parent.verticalCenter ++ } + +- Text { +- text: pageStack.currentPage ? pageStack.currentPage.rightText : "" +- anchors.verticalCenter: parent.verticalCenter +- color: "white" +- font.bold: true +- font.pixelSize: 16 ++ Text { ++ text: pageStack.currentPage ? pageStack.currentPage.rightText : "" ++ anchors.verticalCenter: parent.verticalCenter ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } + } + } + } +@@ -234,9 +413,10 @@ + ListElement { + pageSource: "OverviewHub.qml" + } +- ListElement { +- pageSource: "OverviewTiles.qml" +- } ++//////// (commented out) -- added dynamically above ++// ListElement { ++// pageSource: "OverviewTiles.qml" ++// } + } + + Component { +@@ -255,7 +435,12 @@ + Timer { + interval: 2000 + running: completed && overviewsLoaded && startWithMenu.valid +- onTriggered: if (startWithMenu.value === 0) showOverview() ++ onTriggered: ++ { ++//////// modified for OverviewGridParallelEnhanced page ++ selectHubOverview () ++ if (startWithMenu.value === 0) showOverview() ++ } + } + + function getDefaultOverviewIndex() +@@ -309,11 +494,19 @@ + } + } + ++//////// Modified to append page if oldPage not found + function replaceOverview(oldPage, newPage) + { + for (var i = 0; i < overviewModel.count; i++) ++ { + if (overviewModel.get(i).pageSource === oldPage) ++ { + overviewModel.get(i).pageSource = newPage ++ return ++ } ++ } ++ // here if oldPage wasn't found -- append the new page ++ overviewModel.append({"pageSource": newPage}) + } + + // Central mover for the ball animation on the overviews diff --git a/FileSets/v3.22/main.qml b/FileSets/PatchSource/main.qml-v3.22 similarity index 100% rename from FileSets/v3.22/main.qml rename to FileSets/PatchSource/main.qml-v3.22 diff --git a/FileSets/v3.22/main.qml.orig b/FileSets/PatchSource/main.qml-v3.22.orig similarity index 100% rename from FileSets/v3.22/main.qml.orig rename to FileSets/PatchSource/main.qml-v3.22.orig diff --git a/FileSets/PatchSource/main.qml-v3.22.patch b/FileSets/PatchSource/main.qml-v3.22.patch new file mode 100644 index 00000000..fb43f14f --- /dev/null +++ b/FileSets/PatchSource/main.qml-v3.22.patch @@ -0,0 +1,477 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.22.orig 2024-07-08 09:16:57 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.22 2024-01-24 10:19:41 +@@ -1,3 +1,6 @@ ++//////// Modified to hide the OverviewTiles page ++//////// Modified to substitute flow overview pages ++ + import QtQuick 1.1 + + import Qt.labs.components.native 1.0 +@@ -16,48 +19,108 @@ + property bool completed: false + property bool showAlert: NotificationCenter.alert + property bool alarm: NotificationCenter.alarm +- property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid ++//////// added for GuiMods flow pages ++ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid + property string bindPrefix: "com.victronenergy.settings" + + property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") + property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; + property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; + +- ++//////// modified for GuiMods pages + property string hubOverviewType: theSystem.systemType.valid ? +- withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" ++ withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" ++ property string currentHubOverview: "OverviewHub.qml" ++ property string currentMobileOverview: "" ++ property string currentGeneratorOverview: "" + + // Keep track of the current view (menu/overview) to show as default next time the + // CCGX is restarted + onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) + +- // Add the correct OverviewHub page +- onHubOverviewTypeChanged: { +- switch(hubOverviewType){ +- case "Hub": +- case "Hub-1": +- case "Hub-2": +- case "Hub-3": +- replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); +- break; +- case "Hub-4": +- case "ESS": +- replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); +- break; +- default: +- break; ++ // Add the correct OverviewGridParallelEnhanced page ++//////// modified for OverviewHubEnhanced page ++ onHubOverviewTypeChanged: selectHubOverview () ++ ++ VBusItem ++ { ++ id: guiModsFlowOverview ++ bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" ++ onValueChanged: selectHubOverview () ++ } ++ ++////// GuiMods — DarkMode ++ property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } ++ property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 ++ ++////// GuiMods — DarkMode ++ Rectangle { ++ anchors ++ { ++ fill: parent + } +- // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) +- // by adding a return statement +- return ++ color: !darkMode ? "transparent" : "#202020" ++ z: -1 + } + ++ // base a new hub selection on the hub type and the enhanced flow overview flag ++ function selectHubOverview () ++ { ++ var newHubOverview = currentHubOverview ++ // Victron stock overviews with automatic selection ++ if (guiModsFlowOverview.value == 0) ++ { ++ switch(hubOverviewType){ ++ case "Hub": ++ case "Hub-1": ++ case "Hub-2": ++ case "Hub-3": ++ case "unknown": ++ newHubOverview = "OverviewHub.qml" ++ break; ++ case "Hub-4": ++ case "ESS": ++ newHubOverview = "OverviewGridParallel.qml" ++ break; ++ default: ++ break; ++ } ++ } ++ // Gui Mods simple flow ++ else if (guiModsFlowOverview.value === 1) ++ { ++ newHubOverview = "OverviewHubEnhanced.qml" ++ } ++ // Gui Mods complex flow (AC coupled or DC coupled) ++ else ++ { ++ newHubOverview = "OverviewFlowComplex.qml" ++ } ++ ++ if (newHubOverview != currentHubOverview) ++ { ++ replaceOverview(currentHubOverview, newHubOverview); ++ currentHubOverview = newHubOverview ++ } ++ ++ // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) ++ // by adding a return statement ++ return ++ } ++ + VBusItem { + id: generatorOverview + bind: "com.victronenergy.settings/Settings/Relay/Function" +- onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) ++ onValueChanged: selectGeneratorOverview () + } + ++ VBusItem ++ { ++ id: generatorOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" ++ onValueChanged: selectGeneratorOverview () ++ } ++ + VBusItem { + bind: "com.victronenergy.generator.startstop1/GensetProductId" + onValueChanged: { +@@ -75,21 +138,103 @@ + } + } + +- VBusItem { +- id: mobileOverview +- bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" +- onValueChanged:{ +- extraOverview("OverviewMobile.qml", value === 1) +- } ++ function selectGeneratorOverview () ++ { ++ var newGeneratorOverview ++ if (generatorOverview.value === 1) ++ { ++ if (generatorOverviewEnhanced.value === 1) ++ newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" ++ else ++ newGeneratorOverview = "OverviewGeneratorRelay.qml" ++ if (currentGeneratorOverview === "") ++ extraOverview (newGeneratorOverview, true) ++ else ++ replaceOverview (currentGeneratorOverview, newGeneratorOverview) ++ currentGeneratorOverview = newGeneratorOverview ++ } ++ else ++ { ++ // hide existing generator overview if any ++ if (currentGeneratorOverview != "") ++ { ++ extraOverview (currentGeneratorOverview, false) ++ currentGeneratorOverview = "" ++ } ++ } + } +- VBusItem { +- id: tanksOverview +- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" +- onValueChanged:{ +- extraOverview("OverviewTanks.qml", value === 1) +- } +- } + ++//////// handle OverviewMobileEnhanced page ++ VBusItem ++ { ++ id: mobileOverview ++ bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ VBusItem ++ { ++ id: mobileOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ ++ // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags ++ function selectMobileOverview () ++ { ++ var newMobileOverview ++ if (mobileOverview.value === 1) ++ { ++ if (mobileOverviewEnhanced.value === 1) ++ newMobileOverview = "OverviewMobileEnhanced.qml" ++ else ++ newMobileOverview = "OverviewMobile.qml" ++ if (currentMobileOverview === "") ++ extraOverview (newMobileOverview, true) ++ else ++ replaceOverview (currentMobileOverview, newMobileOverview) ++ currentMobileOverview = newMobileOverview ++ } ++ else ++ { ++ // hide existing mobile overview if any ++ if (currentMobileOverview != "") ++ { ++ extraOverview (currentMobileOverview, false) ++ currentMobileOverview = "" ++ } ++ } ++ } ++ ++//////// show/hide the OverviewTiles page ++ VBusItem ++ { ++ id: showOverviewTiles ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" ++ onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) ++ } ++ ++//////// show/hide the OverviewRelays page ++ VBusItem { ++ id: showOverviewRelays ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" ++ onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) ++ } ++ ++//////// show/hide the Overview Tanks/Temps/Digital Inputs page ++ VBusItem { ++ id: showOverviewTanksTemps ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" ++ onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) ++ } ++ ++ VBusItem { ++ id: tanksOverview ++ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" ++ onValueChanged:{ ++ extraOverview("OverviewTanks.qml", value === 1) ++ } ++ } ++ + VBusItem { + id: startWithMenu + bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" +@@ -169,76 +314,111 @@ + id: mbTools + height: parent.height + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- anchors.left: mbTools.left +- height: mbTools.height +- width: 200 ++//// GuiMods - DarkMode ++ Row ++ { ++ spacing: 0 ++ anchors.fill: parent ++ Item { ++ id: pagesItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 170 + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.leftAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.leftAction(true) ++ } + } ++ ++ Row { ++ anchors.verticalCenter: parent.verticalCenter ++ anchors.horizontalCenter: parent.horizontalCenter ++ ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ } ++ ++ Text { ++ anchors.verticalCenter: parent.verticalCenter ++ text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } ++ } + } + +- Row { +- anchors.centerIn: parent ++ Item { ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width + +- MbIcon { +- anchors.verticalCenter: parent.verticalCenter +- iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ MouseArea ++ { ++ anchors.fill: parent ++ onClicked: ++ { ++ if (darkModeItem.valid) ++ darkModeItem.setValue (! darkMode) ++ } + } + +- Text { +- anchors.verticalCenter: parent.verticalCenter +- text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ Text ++ { ++ anchors.fill: parent ++ horizontalAlignment: Text.AlignHCenter ++ text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) + color: "white" + font.bold: true +- font.pixelSize: 16 ++ font.pixelSize: 12 ++ visible: darkModeItem.valid + } + } +- } +- +- MbIcon { +- id: centerScrollIndicator +- +- anchors { +- horizontalCenter: parent.horizontalCenter +- verticalCenter: mbTools.verticalCenter ++ Item ++ { ++ id: centerScrollIndicator ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 20 ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" ++ } + } +- iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" +- } + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- height: mbTools.height +- anchors.right: mbTools.right +- width: 200 ++ Item { ++ id: menusItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: pagesItem.width + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.rightAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.rightAction(true) ++ } + } +- } + +- Row { +- anchors.centerIn: parent ++ Row { ++ anchors.centerIn: parent + +- MbIcon { +- iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" +- anchors.verticalCenter: parent.verticalCenter +- } ++ MbIcon { ++ iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" ++ anchors.verticalCenter: parent.verticalCenter ++ } + +- Text { +- text: pageStack.currentPage ? pageStack.currentPage.rightText : "" +- anchors.verticalCenter: parent.verticalCenter +- color: "white" +- font.bold: true +- font.pixelSize: 16 ++ Text { ++ text: pageStack.currentPage ? pageStack.currentPage.rightText : "" ++ anchors.verticalCenter: parent.verticalCenter ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } + } + } + } +@@ -253,9 +433,10 @@ + ListElement { + pageSource: "OverviewHub.qml" + } +- ListElement { +- pageSource: "OverviewTiles.qml" +- } ++//////// (commented out) -- added dynamically above ++// ListElement { ++// pageSource: "OverviewTiles.qml" ++// } + } + + Component { +@@ -274,7 +455,12 @@ + Timer { + interval: 2000 + running: completed && overviewsLoaded && startWithMenu.valid +- onTriggered: if (startWithMenu.value === 0) showOverview() ++ onTriggered: ++ { ++//////// modified for OverviewGridParallelEnhanced page ++ selectHubOverview () ++ if (startWithMenu.value === 0) showOverview() ++ } + } + + function getDefaultOverviewIndex() +@@ -297,6 +483,7 @@ + Component { + id: offlineFwUpdates + PageSettingsFirmwareOffline { checkOnCompleted: true} ++ + } + + // Add or remove extra overviews. for example, generator overview +@@ -327,11 +514,19 @@ + } + } + ++//////// Modified to append page if oldPage not found + function replaceOverview(oldPage, newPage) + { + for (var i = 0; i < overviewModel.count; i++) ++ { + if (overviewModel.get(i).pageSource === oldPage) ++ { + overviewModel.get(i).pageSource = newPage ++ return ++ } ++ } ++ // here if oldPage wasn't found -- append the new page ++ overviewModel.append({"pageSource": newPage}) + } + + // Central mover for the ball animation on the overviews diff --git a/FileSets/v3.40~13/main.qml b/FileSets/PatchSource/main.qml-v3.40~13 similarity index 100% rename from FileSets/v3.40~13/main.qml rename to FileSets/PatchSource/main.qml-v3.40~13 diff --git a/FileSets/v3.40~13/main.qml.orig b/FileSets/PatchSource/main.qml-v3.40~13.orig similarity index 100% rename from FileSets/v3.40~13/main.qml.orig rename to FileSets/PatchSource/main.qml-v3.40~13.orig diff --git a/FileSets/PatchSource/main.qml-v3.40~13.patch b/FileSets/PatchSource/main.qml-v3.40~13.patch new file mode 100644 index 00000000..1f865d9c --- /dev/null +++ b/FileSets/PatchSource/main.qml-v3.40~13.patch @@ -0,0 +1,477 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.40~13.orig 2024-07-08 09:16:57 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.40~13 2024-05-14 07:00:12 +@@ -1,3 +1,6 @@ ++//////// Modified to hide the OverviewTiles page ++//////// Modified to substitute flow overview pages ++ + import QtQuick 1.1 + + import Qt.labs.components.native 1.0 +@@ -16,48 +19,108 @@ + property bool completed: false + property bool alarm: alarmNotification.valid ? alarmNotification.value : 0 + property bool showAlert: alertNotification.valid ? alertNotification.value : 0 +- property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid ++//////// added for GuiMods flow pages ++ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid + property string bindPrefix: "com.victronenergy.settings" + + property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") + property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; + property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; + +- ++//////// modified for GuiMods pages + property string hubOverviewType: theSystem.systemType.valid ? +- withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" ++ withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" ++ property string currentHubOverview: "OverviewHub.qml" ++ property string currentMobileOverview: "" ++ property string currentGeneratorOverview: "" + + // Keep track of the current view (menu/overview) to show as default next time the + // CCGX is restarted + onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) + +- // Add the correct OverviewHub page +- onHubOverviewTypeChanged: { +- switch(hubOverviewType){ +- case "Hub": +- case "Hub-1": +- case "Hub-2": +- case "Hub-3": +- replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); +- break; +- case "Hub-4": +- case "ESS": +- replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); +- break; +- default: +- break; ++ // Add the correct OverviewGridParallelEnhanced page ++//////// modified for OverviewHubEnhanced page ++ onHubOverviewTypeChanged: selectHubOverview () ++ ++ VBusItem ++ { ++ id: guiModsFlowOverview ++ bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" ++ onValueChanged: selectHubOverview () ++ } ++ ++////// GuiMods — DarkMode ++ property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } ++ property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 ++ ++////// GuiMods — DarkMode ++ Rectangle { ++ anchors ++ { ++ fill: parent + } +- // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) +- // by adding a return statement +- return ++ color: !darkMode ? "transparent" : "#202020" ++ z: -1 + } + ++ // base a new hub selection on the hub type and the enhanced flow overview flag ++ function selectHubOverview () ++ { ++ var newHubOverview = currentHubOverview ++ // Victron stock overviews with automatic selection ++ if (guiModsFlowOverview.value == 0) ++ { ++ switch(hubOverviewType){ ++ case "Hub": ++ case "Hub-1": ++ case "Hub-2": ++ case "Hub-3": ++ case "unknown": ++ newHubOverview = "OverviewHub.qml" ++ break; ++ case "Hub-4": ++ case "ESS": ++ newHubOverview = "OverviewGridParallel.qml" ++ break; ++ default: ++ break; ++ } ++ } ++ // Gui Mods simple flow ++ else if (guiModsFlowOverview.value === 1) ++ { ++ newHubOverview = "OverviewHubEnhanced.qml" ++ } ++ // Gui Mods complex flow (AC coupled or DC coupled) ++ else ++ { ++ newHubOverview = "OverviewFlowComplex.qml" ++ } ++ ++ if (newHubOverview != currentHubOverview) ++ { ++ replaceOverview(currentHubOverview, newHubOverview); ++ currentHubOverview = newHubOverview ++ } ++ ++ // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) ++ // by adding a return statement ++ return ++ } ++ + VBusItem { + id: generatorOverview + bind: "com.victronenergy.settings/Settings/Relay/Function" +- onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) ++ onValueChanged: selectGeneratorOverview () + } + ++ VBusItem ++ { ++ id: generatorOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" ++ onValueChanged: selectGeneratorOverview () ++ } ++ + VBusItem { + bind: "com.victronenergy.generator.startstop1/GensetProductId" + onValueChanged: { +@@ -75,21 +138,103 @@ + } + } + +- VBusItem { +- id: mobileOverview +- bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" +- onValueChanged:{ +- extraOverview("OverviewMobile.qml", value === 1) +- } ++ function selectGeneratorOverview () ++ { ++ var newGeneratorOverview ++ if (generatorOverview.value === 1) ++ { ++ if (generatorOverviewEnhanced.value === 1) ++ newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" ++ else ++ newGeneratorOverview = "OverviewGeneratorRelay.qml" ++ if (currentGeneratorOverview === "") ++ extraOverview (newGeneratorOverview, true) ++ else ++ replaceOverview (currentGeneratorOverview, newGeneratorOverview) ++ currentGeneratorOverview = newGeneratorOverview ++ } ++ else ++ { ++ // hide existing generator overview if any ++ if (currentGeneratorOverview != "") ++ { ++ extraOverview (currentGeneratorOverview, false) ++ currentGeneratorOverview = "" ++ } ++ } + } +- VBusItem { +- id: tanksOverview +- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" +- onValueChanged:{ +- extraOverview("OverviewTanks.qml", value === 1) +- } +- } + ++//////// handle OverviewMobileEnhanced page ++ VBusItem ++ { ++ id: mobileOverview ++ bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ VBusItem ++ { ++ id: mobileOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ ++ // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags ++ function selectMobileOverview () ++ { ++ var newMobileOverview ++ if (mobileOverview.value === 1) ++ { ++ if (mobileOverviewEnhanced.value === 1) ++ newMobileOverview = "OverviewMobileEnhanced.qml" ++ else ++ newMobileOverview = "OverviewMobile.qml" ++ if (currentMobileOverview === "") ++ extraOverview (newMobileOverview, true) ++ else ++ replaceOverview (currentMobileOverview, newMobileOverview) ++ currentMobileOverview = newMobileOverview ++ } ++ else ++ { ++ // hide existing mobile overview if any ++ if (currentMobileOverview != "") ++ { ++ extraOverview (currentMobileOverview, false) ++ currentMobileOverview = "" ++ } ++ } ++ } ++ ++//////// show/hide the OverviewTiles page ++ VBusItem ++ { ++ id: showOverviewTiles ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" ++ onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) ++ } ++ ++//////// show/hide the OverviewRelays page ++ VBusItem { ++ id: showOverviewRelays ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" ++ onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) ++ } ++ ++//////// show/hide the Overview Tanks/Temps/Digital Inputs page ++ VBusItem { ++ id: showOverviewTanksTemps ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" ++ onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) ++ } ++ ++ VBusItem { ++ id: tanksOverview ++ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" ++ onValueChanged:{ ++ extraOverview("OverviewTanks.qml", value === 1) ++ } ++ } ++ + VBusItem { + id: startWithMenu + bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" +@@ -179,76 +324,111 @@ + id: mbTools + height: parent.height + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- anchors.left: mbTools.left +- height: mbTools.height +- width: 200 ++//// GuiMods - DarkMode ++ Row ++ { ++ spacing: 0 ++ anchors.fill: parent ++ Item { ++ id: pagesItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 170 + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.leftAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.leftAction(true) ++ } + } ++ ++ Row { ++ anchors.verticalCenter: parent.verticalCenter ++ anchors.horizontalCenter: parent.horizontalCenter ++ ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ } ++ ++ Text { ++ anchors.verticalCenter: parent.verticalCenter ++ text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } ++ } + } + +- Row { +- anchors.centerIn: parent ++ Item { ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width + +- MbIcon { +- anchors.verticalCenter: parent.verticalCenter +- iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ MouseArea ++ { ++ anchors.fill: parent ++ onClicked: ++ { ++ if (darkModeItem.valid) ++ darkModeItem.setValue (! darkMode) ++ } + } + +- Text { +- anchors.verticalCenter: parent.verticalCenter +- text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ Text ++ { ++ anchors.fill: parent ++ horizontalAlignment: Text.AlignHCenter ++ text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) + color: "white" + font.bold: true +- font.pixelSize: 16 ++ font.pixelSize: 12 ++ visible: darkModeItem.valid + } + } +- } +- +- MbIcon { +- id: centerScrollIndicator +- +- anchors { +- horizontalCenter: parent.horizontalCenter +- verticalCenter: mbTools.verticalCenter ++ Item ++ { ++ id: centerScrollIndicator ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 20 ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" ++ } + } +- iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" +- } + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- height: mbTools.height +- anchors.right: mbTools.right +- width: 200 ++ Item { ++ id: menusItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: pagesItem.width + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.rightAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.rightAction(true) ++ } + } +- } + +- Row { +- anchors.centerIn: parent ++ Row { ++ anchors.centerIn: parent + +- MbIcon { +- iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" +- anchors.verticalCenter: parent.verticalCenter +- } ++ MbIcon { ++ iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" ++ anchors.verticalCenter: parent.verticalCenter ++ } + +- Text { +- text: pageStack.currentPage ? pageStack.currentPage.rightText : "" +- anchors.verticalCenter: parent.verticalCenter +- color: "white" +- font.bold: true +- font.pixelSize: 16 ++ Text { ++ text: pageStack.currentPage ? pageStack.currentPage.rightText : "" ++ anchors.verticalCenter: parent.verticalCenter ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } + } + } + } +@@ -263,9 +443,10 @@ + ListElement { + pageSource: "OverviewHub.qml" + } +- ListElement { +- pageSource: "OverviewTiles.qml" +- } ++//////// (commented out) -- added dynamically above ++// ListElement { ++// pageSource: "OverviewTiles.qml" ++// } + } + + Component { +@@ -284,7 +465,12 @@ + Timer { + interval: 2000 + running: completed && overviewsLoaded && startWithMenu.valid +- onTriggered: if (startWithMenu.value === 0) showOverview() ++ onTriggered: ++ { ++//////// modified for OverviewGridParallelEnhanced page ++ selectHubOverview () ++ if (startWithMenu.value === 0) showOverview() ++ } + } + + function getDefaultOverviewIndex() +@@ -307,6 +493,7 @@ + Component { + id: offlineFwUpdates + PageSettingsFirmwareOffline { checkOnCompleted: true} ++ + } + + // Add or remove extra overviews. for example, generator overview +@@ -337,11 +524,19 @@ + } + } + ++//////// Modified to append page if oldPage not found + function replaceOverview(oldPage, newPage) + { + for (var i = 0; i < overviewModel.count; i++) ++ { + if (overviewModel.get(i).pageSource === oldPage) ++ { + overviewModel.get(i).pageSource = newPage ++ return ++ } ++ } ++ // here if oldPage wasn't found -- append the new page ++ overviewModel.append({"pageSource": newPage}) + } + + // Central mover for the ball animation on the overviews diff --git a/FileSets/v3.40~35/main.qml b/FileSets/PatchSource/main.qml-v3.40~35 similarity index 100% rename from FileSets/v3.40~35/main.qml rename to FileSets/PatchSource/main.qml-v3.40~35 diff --git a/FileSets/v3.40~35/main.qml.orig b/FileSets/PatchSource/main.qml-v3.40~35.orig similarity index 100% rename from FileSets/v3.40~35/main.qml.orig rename to FileSets/PatchSource/main.qml-v3.40~35.orig diff --git a/FileSets/PatchSource/main.qml-v3.40~35.patch b/FileSets/PatchSource/main.qml-v3.40~35.patch new file mode 100644 index 00000000..f3466692 --- /dev/null +++ b/FileSets/PatchSource/main.qml-v3.40~35.patch @@ -0,0 +1,455 @@ +--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.40~35.orig 2024-07-08 09:16:57 ++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.40~35 2024-05-17 07:34:02 +@@ -1,3 +1,6 @@ ++//////// Modified to hide the OverviewTiles page ++//////// Modified to substitute flow overview pages ++ + import QtQuick 1.1 + + import Qt.labs.components.native 1.0 +@@ -16,7 +19,8 @@ + property bool completed: false + property bool alarm: alarmNotification.valid ? alarmNotification.value : 0 + property bool showAlert: alertNotification.valid ? alertNotification.value : 0 +- property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid ++//////// added for GuiMods flow pages ++ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid + property string bindPrefix: "com.victronenergy.settings" + + property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") +@@ -25,25 +29,83 @@ + + property bool hasGridMeter: theSystem.hasGridMeter + ++//////// modified for GuiMods pages ++ property string currentHubOverview: "OverviewHub.qml" ++ property string currentMobileOverview: "" ++ property string currentGeneratorOverview: "" ++ + // Keep track of the current view (menu/overview) to show as default next time the + // CCGX is restarted + onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) + + // Add the correct OverviewHub page +- onHasGridMeterChanged: { +- if (hasGridMeter) { +- replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); +- } else { +- replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); ++//////// modified for OverviewHubEnhanced page ++ onHasGridMeterChanged: selectHubOverview () ++ ++ VBusItem ++ { ++ id: guiModsFlowOverview ++ bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" ++ onValueChanged: selectHubOverview () ++ } ++ ++////// GuiMods — DarkMode ++ property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } ++ property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 ++ ++////// GuiMods — DarkMode ++ Rectangle { ++ anchors ++ { ++ fill: parent + } ++ color: !darkMode ? "transparent" : "#202020" ++ z: -1 + } + ++ // base a new hub selection on the hub type and the enhanced flow overview flag ++ function selectHubOverview () ++ { ++ var newHubOverview = currentHubOverview ++ // Victron stock overviews with automatic selection ++ if (guiModsFlowOverview.value == 0) ++ { ++ if (hasGridMeter) ++ newHubOverview = "OverviewGridParallel.qml" ++ else ++ newHubOverview = "OverviewHub.qml" ++ } ++ // Gui Mods simple flow ++ else if (guiModsFlowOverview.value === 1) ++ { ++ newHubOverview = "OverviewHubEnhanced.qml" ++ } ++ // Gui Mods complex flow (AC coupled or DC coupled) ++ else ++ { ++ newHubOverview = "OverviewFlowComplex.qml" ++ } ++ ++ if (newHubOverview != currentHubOverview) ++ { ++ replaceOverview(currentHubOverview, newHubOverview); ++ currentHubOverview = newHubOverview ++ } ++ } ++ + VBusItem { + id: generatorOverview + bind: "com.victronenergy.settings/Settings/Relay/Function" +- onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) ++ onValueChanged: selectGeneratorOverview () + } + ++ VBusItem ++ { ++ id: generatorOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" ++ onValueChanged: selectGeneratorOverview () ++ } ++ + VBusItem { + bind: "com.victronenergy.generator.startstop1/GensetProductId" + onValueChanged: { +@@ -52,7 +114,7 @@ + + // Show generic overview for ComAp and DSE + extraOverview("OverviewGeneratorOther.qml", +- [0xB044, 0xB046, 0xB048, 0xB049].indexOf(value) > -1) ++ [0xB044, 0xB046, 0xB048, 0xB049].indexOf(value) > -1) + + // Switch to FP overview in case it is the default one + if (isOverviewPage) { +@@ -61,20 +123,102 @@ + } + } + +- VBusItem { +- id: mobileOverview +- bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" +- onValueChanged:{ +- extraOverview("OverviewMobile.qml", value === 1) +- } ++ function selectGeneratorOverview () ++ { ++ var newGeneratorOverview ++ if (generatorOverview.value === 1) ++ { ++ if (generatorOverviewEnhanced.value === 1) ++ newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" ++ else ++ newGeneratorOverview = "OverviewGeneratorRelay.qml" ++ if (currentGeneratorOverview === "") ++ extraOverview (newGeneratorOverview, true) ++ else ++ replaceOverview (currentGeneratorOverview, newGeneratorOverview) ++ currentGeneratorOverview = newGeneratorOverview ++ } ++ else ++ { ++ // hide existing generator overview if any ++ if (currentGeneratorOverview != "") ++ { ++ extraOverview (currentGeneratorOverview, false) ++ currentGeneratorOverview = "" ++ } ++ } + } +- VBusItem { +- id: tanksOverview +- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" +- onValueChanged:{ +- extraOverview("OverviewTanks.qml", value === 1) +- } +- } ++ ++//////// handle OverviewMobileEnhanced page ++ VBusItem ++ { ++ id: mobileOverview ++ bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ VBusItem ++ { ++ id: mobileOverviewEnhanced ++ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" ++ onValueChanged: selectMobileOverview () ++ } ++ ++ // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags ++ function selectMobileOverview () ++ { ++ var newMobileOverview ++ if (mobileOverview.value === 1) ++ { ++ if (mobileOverviewEnhanced.value === 1) ++ newMobileOverview = "OverviewMobileEnhanced.qml" ++ else ++ newMobileOverview = "OverviewMobile.qml" ++ if (currentMobileOverview === "") ++ extraOverview (newMobileOverview, true) ++ else ++ replaceOverview (currentMobileOverview, newMobileOverview) ++ currentMobileOverview = newMobileOverview ++ } ++ else ++ { ++ // hide existing mobile overview if any ++ if (currentMobileOverview != "") ++ { ++ extraOverview (currentMobileOverview, false) ++ currentMobileOverview = "" ++ } ++ } ++ } ++ ++//////// show/hide the OverviewTiles page ++ VBusItem ++ { ++ id: showOverviewTiles ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" ++ onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) ++ } ++ ++//////// show/hide the OverviewRelays page ++ VBusItem { ++ id: showOverviewRelays ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" ++ onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) ++ } ++ ++//////// show/hide the Overview Tanks/Temps/Digital Inputs page ++ VBusItem { ++ id: showOverviewTanksTemps ++ bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" ++ onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) ++ } ++ ++ VBusItem { ++ id: tanksOverview ++ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" ++ onValueChanged:{ ++ extraOverview("OverviewTanks.qml", value === 1) ++ } ++ } + + VBusItem { + id: startWithMenu +@@ -165,76 +309,111 @@ + id: mbTools + height: parent.height + +- Item { +- anchors.verticalCenter: parent.verticalCenter +- anchors.left: mbTools.left +- height: mbTools.height +- width: 200 ++//// GuiMods - DarkMode ++ Row ++ { ++ spacing: 0 ++ anchors.fill: parent ++ Item { ++ id: pagesItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 170 + +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.leftAction(true) ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.leftAction(true) ++ } + } ++ ++ Row { ++ anchors.verticalCenter: parent.verticalCenter ++ anchors.horizontalCenter: parent.horizontalCenter ++ ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ } ++ ++ Text { ++ anchors.verticalCenter: parent.verticalCenter ++ text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } ++ } + } + +- Row { +- anchors.centerIn: parent ++ Item { ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width + +- MbIcon { +- anchors.verticalCenter: parent.verticalCenter +- iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" ++ MouseArea ++ { ++ anchors.fill: parent ++ onClicked: ++ { ++ if (darkModeItem.valid) ++ darkModeItem.setValue (! darkMode) ++ } + } + +- Text { +- anchors.verticalCenter: parent.verticalCenter +- text: pageStack.currentPage ? pageStack.currentPage.leftText : "" ++ Text ++ { ++ anchors.fill: parent ++ horizontalAlignment: Text.AlignHCenter ++ text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) + color: "white" + font.bold: true +- font.pixelSize: 16 ++ font.pixelSize: 12 ++ visible: darkModeItem.valid + } + } +- } +- +- MbIcon { +- id: centerScrollIndicator +- +- anchors { +- horizontalCenter: parent.horizontalCenter +- verticalCenter: mbTools.verticalCenter +- } +- iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" +- } +- +- Item { +- anchors.verticalCenter: parent.verticalCenter +- height: mbTools.height +- anchors.right: mbTools.right +- width: 200 +- +- MouseArea { +- anchors.fill: parent +- onClicked: { +- if (pageStack.currentPage) +- pageStack.currentPage.toolbarHandler.rightAction(true) ++ Item ++ { ++ id: centerScrollIndicator ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: 20 ++ MbIcon { ++ anchors.verticalCenter: parent.verticalCenter ++ iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" + } + } + +- Row { +- anchors.centerIn: parent ++ Item { ++ id: menusItem ++ anchors.verticalCenter: parent.verticalCenter ++ height: mbTools.height ++ width: pagesItem.width + +- MbIcon { +- iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" +- anchors.verticalCenter: parent.verticalCenter ++ MouseArea { ++ anchors.fill: parent ++ onClicked: { ++ if (pageStack.currentPage) ++ pageStack.currentPage.toolbarHandler.rightAction(true) ++ } + } + +- Text { +- text: pageStack.currentPage ? pageStack.currentPage.rightText : "" +- anchors.verticalCenter: parent.verticalCenter +- color: "white" +- font.bold: true +- font.pixelSize: 16 ++ Row { ++ anchors.centerIn: parent ++ ++ MbIcon { ++ iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" ++ anchors.verticalCenter: parent.verticalCenter ++ } ++ ++ Text { ++ text: pageStack.currentPage ? pageStack.currentPage.rightText : "" ++ anchors.verticalCenter: parent.verticalCenter ++ color: "white" ++ font.bold: true ++ font.pixelSize: 16 ++ } + } + } + } +@@ -249,9 +428,10 @@ + ListElement { + pageSource: "OverviewHub.qml" + } +- ListElement { +- pageSource: "OverviewTiles.qml" +- } ++//////// (commented out) -- added dynamically above ++// ListElement { ++// pageSource: "OverviewTiles.qml" ++// } + } + + Component { +@@ -270,7 +450,12 @@ + Timer { + interval: 2000 + running: completed && overviewsLoaded && startWithMenu.valid +- onTriggered: if (startWithMenu.value === 0) showOverview() ++ onTriggered: ++ { ++//////// modified for OverviewGridParallelEnhanced page ++ selectHubOverview () ++ if (startWithMenu.value === 0) showOverview() ++ } + } + + function getDefaultOverviewIndex() +@@ -293,6 +478,7 @@ + Component { + id: offlineFwUpdates + PageSettingsFirmwareOffline { checkOnCompleted: true} ++ + } + + // Add or remove extra overviews. for example, generator overview +@@ -323,11 +509,19 @@ + } + } + ++//////// Modified to append page if oldPage not found + function replaceOverview(oldPage, newPage) + { + for (var i = 0; i < overviewModel.count; i++) ++ { + if (overviewModel.get(i).pageSource === oldPage) ++ { + overviewModel.get(i).pageSource = newPage ++ return ++ } ++ } ++ // here if oldPage wasn't found -- append the new page ++ overviewModel.append({"pageSource": newPage}) + } + + // Central mover for the ball animation on the overviews diff --git a/FileSets/fileList b/FileSets/fileList index 313ec592..9143c029 100644 --- a/FileSets/fileList +++ b/FileSets/fileList @@ -1,4 +1,3 @@ -/opt/victronenergy/dbus-systemcalc-py/dbus_systemcalc.py /opt/victronenergy/gui/qml/Battery.qml /opt/victronenergy/gui/qml/DetailAcInput.qml /opt/victronenergy/gui/qml/DetailInverter.qml @@ -31,7 +30,6 @@ /opt/victronenergy/gui/qml/OverviewTanks.qml /opt/victronenergy/gui/qml/OverviewTanksTempsDigInputs.qml /opt/victronenergy/gui/qml/PageGenerator.qml -/opt/victronenergy/gui/qml/PageMain.qml /opt/victronenergy/gui/qml/PageSettingsDisplay.qml /opt/victronenergy/gui/qml/PageSettingsGenerator.qml /opt/victronenergy/gui/qml/PageSettingsGuiMods.qml @@ -41,7 +39,6 @@ /opt/victronenergy/gui/qml/TileDigIn.qml /opt/victronenergy/gui/qml/TileRelay.qml /opt/victronenergy/gui/qml/TileText.qml -/opt/victronenergy/gui/qml/main.qml /var/www/venus/styling/styles.css /opt/victronenergy/gui/qml/PageDigitalInput.qml /opt/victronenergy/gui/qml/MbItemDigitalInput.qml diff --git a/FileSets/fileListPatched b/FileSets/fileListPatched index 045433d7..fb5f25d3 100644 --- a/FileSets/fileListPatched +++ b/FileSets/fileListPatched @@ -1 +1,4 @@ /opt/victronenergy/dbus-modbustcp/attributes.csv +/opt/victronenergy/dbus-systemcalc-py/dbus_systemcalc.py +/opt/victronenergy/gui/qml/main.qml +/opt/victronenergy/gui/qml/PageMain.qml diff --git a/FileSets/v2.73/PageMain.qml b/FileSets/v2.73/PageMain.qml deleted file mode 100644 index 47fbfee4..00000000 --- a/FileSets/v2.73/PageMain.qml +++ /dev/null @@ -1,251 +0,0 @@ -//////// modified order to put Settings, then Notifications at top of list - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} - property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 - - model: VisualModels { -//////// put Settings at top of list - VisualItemModel { - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: settingsAtTop - } - -//////// put Notifications second - MbSubMenu { - id: menuNotificationsTop - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: !settingsAtTop - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: !settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: !settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v2.73/PageMain.qml.orig b/FileSets/v2.73/PageMain.qml.orig deleted file mode 100644 index 138237d9..00000000 --- a/FileSets/v2.73/PageMain.qml.orig +++ /dev/null @@ -1,210 +0,0 @@ -import QtQuick 1.1 -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - - model: VisualModels { - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v2.84/LINKS_ONLY b/FileSets/v2.84/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v2.89/PageMain.qml b/FileSets/v2.89/PageMain.qml deleted file mode 100644 index c4108024..00000000 --- a/FileSets/v2.89/PageMain.qml +++ /dev/null @@ -1,274 +0,0 @@ -//////// modified order to put Settings, then Notifications at top of list - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} - property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 - - model: VisualModels { -//////// put Settings at top of list - VisualItemModel { - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: settingsAtTop - } - -//////// put Notifications second - MbSubMenu { - id: menuNotificationsTop - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: !settingsAtTop - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: !settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: !settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_ALTERNATOR: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v2.89/PageMain.qml.orig b/FileSets/v2.89/PageMain.qml.orig deleted file mode 100644 index 3a9ca4b5..00000000 --- a/FileSets/v2.89/PageMain.qml.orig +++ /dev/null @@ -1,233 +0,0 @@ -import QtQuick 1.1 -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - - model: VisualModels { - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_ALTERNATOR: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v2.89/main.qml b/FileSets/v2.89/main.qml deleted file mode 100644 index 76f28080..00000000 --- a/FileSets/v2.89/main.qml +++ /dev/null @@ -1,558 +0,0 @@ -//////// Modified to hide the OverviewTiles page -//////// Modified to substitute flow overview pages - -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm -//////// added for GuiMods flow pages - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - -//////// modified for GuiMods pages - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" - property string currentHubOverview: "OverviewHub.qml" - property string currentMobileOverview: "" - property string currentGeneratorOverview: "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewGridParallelEnhanced page -//////// modified for OverviewHubEnhanced page - onHubOverviewTypeChanged: selectHubOverview () - - VBusItem - { - id: guiModsFlowOverview - bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" - onValueChanged: selectHubOverview () - } - -////// GuiMods — DarkMode - property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } - property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 - -////// GuiMods — DarkMode - Rectangle { - anchors - { - fill: parent - } - color: !darkMode ? "transparent" : "#202020" - z: -1 - } - - // base a new hub selection on the hub type and the enhanced flow overview flag - function selectHubOverview () - { - var newHubOverview = currentHubOverview - // Victron stock overviews with automatic selection - if (guiModsFlowOverview.value == 0) - { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - case "unknown": - newHubOverview = "OverviewHub.qml" - break; - case "Hub-4": - case "ESS": - newHubOverview = "OverviewGridParallel.qml" - break; - default: - break; - } - } - // Gui Mods simple flow - else if (guiModsFlowOverview.value === 1) - { - newHubOverview = "OverviewHubEnhanced.qml" - } - // Gui Mods complex flow (AC coupled or DC coupled) - else - { - newHubOverview = "OverviewFlowComplex.qml" - } - - if (newHubOverview != currentHubOverview) - { - replaceOverview(currentHubOverview, newHubOverview); - currentHubOverview = newHubOverview - } - - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: selectGeneratorOverview () - } - - VBusItem - { - id: generatorOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" - onValueChanged: selectGeneratorOverview () - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.settings/Settings/Services/FischerPandaAutoStartStop" - onValueChanged: extraOverview("OverviewGeneratorFp.qml", value === 1) - } - - function selectGeneratorOverview () - { - var newGeneratorOverview - if (generatorOverview.value === 1) - { - if (generatorOverviewEnhanced.value === 1) - newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" - else - newGeneratorOverview = "OverviewGeneratorRelay.qml" - if (currentGeneratorOverview === "") - extraOverview (newGeneratorOverview, true) - else - replaceOverview (currentGeneratorOverview, newGeneratorOverview) - currentGeneratorOverview = newGeneratorOverview - } - else - { - // hide existing generator overview if any - if (currentGeneratorOverview != "") - { - extraOverview (currentGeneratorOverview, false) - currentGeneratorOverview = "" - } - } - } - -//////// handle OverviewMobileEnhanced page - VBusItem - { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged: selectMobileOverview () - } - VBusItem - { - id: mobileOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" - onValueChanged: selectMobileOverview () - } - - // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags - function selectMobileOverview () - { - var newMobileOverview - if (mobileOverview.value === 1) - { - if (mobileOverviewEnhanced.value === 1) - newMobileOverview = "OverviewMobileEnhanced.qml" - else - newMobileOverview = "OverviewMobile.qml" - if (currentMobileOverview === "") - extraOverview (newMobileOverview, true) - else - replaceOverview (currentMobileOverview, newMobileOverview) - currentMobileOverview = newMobileOverview - } - else - { - // hide existing mobile overview if any - if (currentMobileOverview != "") - { - extraOverview (currentMobileOverview, false) - currentMobileOverview = "" - } - } - } - -//////// show/hide the OverviewTiles page - VBusItem - { - id: showOverviewTiles - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" - onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) - } - -//////// show/hide the OverviewRelays page - VBusItem { - id: showOverviewRelays - bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" - onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) - } - -//////// show/hide the Overview Tanks/Temps/Digital Inputs page - VBusItem { - id: showOverviewTanksTemps - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" - onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) - } - - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - -//// GuiMods - DarkMode - Row - { - spacing: 0 - anchors.fill: parent - Item { - id: pagesItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 170 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width - - MouseArea - { - anchors.fill: parent - onClicked: - { - if (darkModeItem.valid) - darkModeItem.setValue (! darkMode) - } - } - - Text - { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) - color: "white" - font.bold: true - font.pixelSize: 12 - visible: darkModeItem.valid - } - } - Item - { - id: centerScrollIndicator - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 20 - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - } - - Item { - id: menusItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: pagesItem.width - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } -//////// (commented out) -- added dynamically above -// ListElement { -// pageSource: "OverviewTiles.qml" -// } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: - { -//////// modified for OverviewGridParallelEnhanced page - selectHubOverview () - if (startWithMenu.value === 0) showOverview() - } - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - -//////// Modified to append page if oldPage not found - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - { - if (overviewModel.get(i).pageSource === oldPage) - { - overviewModel.get(i).pageSource = newPage - return - } - } - // here if oldPage wasn't found -- append the new page - overviewModel.append({"pageSource": newPage}) - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } -} diff --git a/FileSets/v2.89/main.qml.orig b/FileSets/v2.89/main.qml.orig deleted file mode 100644 index cc814c4a..00000000 --- a/FileSets/v2.89/main.qml.orig +++ /dev/null @@ -1,364 +0,0 @@ -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - - - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewHub page - onHubOverviewTypeChanged: { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); - break; - case "Hub-4": - case "ESS": - replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); - break; - default: - break; - } - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.settings/Settings/Services/FischerPandaAutoStartStop" - onValueChanged: extraOverview("OverviewGeneratorFp.qml", value === 1) - } - - VBusItem { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged:{ - extraOverview("OverviewMobile.qml", value === 1) - } - } - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - - Item { - anchors.verticalCenter: parent.verticalCenter - anchors.left: mbTools.left - height: mbTools.height - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - MbIcon { - id: centerScrollIndicator - - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: mbTools.verticalCenter - } - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - anchors.right: mbTools.right - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } - ListElement { - pageSource: "OverviewTiles.qml" - } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: if (startWithMenu.value === 0) showOverview() - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === oldPage) - overviewModel.get(i).pageSource = newPage - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } -} diff --git a/FileSets/v2.92/LINKS_ONLY b/FileSets/v2.92/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v2.92/dbus_systemcalc.py b/FileSets/v2.92/dbus_systemcalc.py deleted file mode 100755 index fbdcba20..00000000 --- a/FileSets/v2.92/dbus_systemcalc.py +++ /dev/null @@ -1,1393 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.103' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.pvinverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/Position': dummy, - '/ProductId': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - #delegates.BydCurrentSense(self), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self)] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._dbusservice.add_path( - '/PvInvertersProductIds', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/NumberOfPhases': {'gettext': '%d'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/Soc': {'gettext': '%.0F %%'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the first battery service - if len(batteries) > 0: - return sorted(batteries)[0] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatepvinverterspidlist(self): - # Create list of connected pv inverters id's - pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter') - productids = [] - - for pvinverter in pvinverters: - pid = self._dbusmonitor.get_value(pvinverter, '/ProductId') - if pid is not None and pid not in productids: - productids.append(pid) - self._dbusservice['/PvInvertersProductIds'] = productids - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter') - pos = {0: '/Ac/PvOnGrid', 1: '/Ac/PvOnOutput', 2: '/Ac/PvOnGenset'} - for pvinverter in pvinverters: - # Position will be None if PV inverter service has just been removed (after retrieving the - # service list). - position = pos.get(self._dbusmonitor.get_value(pvinverter, '/Position')) - if position is not None: - for phase in range(1, 4): - power = self._dbusmonitor.get_value(pvinverter, '/Ac/L%s/Power' % phase) - if power is not None: - path = '%s/L%s/Power' % (position, phase) - newvalues[path] = _safeadd(newvalues.get(path), power) - - current = self._dbusmonitor.get_value(pvinverter, '/Ac/L%s/Current' % phase) - if current is not None: - path = '%s/L%s/Current' % (position, phase) - newvalues[path] = _safeadd(newvalues.get(path), current) - - for path in pos.values(): - self._compute_number_of_phases(path, newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### changed for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/Soc'] = self._dbusmonitor.get_value(self._batteryservice,'/Soc') - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power += self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[2] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - self._updatepvinverterspidlist() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v2.92/dbus_systemcalc.py.orig b/FileSets/v2.92/dbus_systemcalc.py.orig deleted file mode 100755 index 21c59da3..00000000 --- a/FileSets/v2.92/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1193 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.103' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.pvinverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/Position': dummy, - '/ProductId': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - #delegates.BydCurrentSense(self), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self)] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._dbusservice.add_path( - '/PvInvertersProductIds', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGrid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGrid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/PvOnGenset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/PvOnGenset/NumberOfPhases': {'gettext': '%d'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/Soc': {'gettext': '%.0F %%'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the first battery service - if len(batteries) > 0: - return sorted(batteries)[0] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatepvinverterspidlist(self): - # Create list of connected pv inverters id's - pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter') - productids = [] - - for pvinverter in pvinverters: - pid = self._dbusmonitor.get_value(pvinverter, '/ProductId') - if pid is not None and pid not in productids: - productids.append(pid) - self._dbusservice['/PvInvertersProductIds'] = productids - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter') - pos = {0: '/Ac/PvOnGrid', 1: '/Ac/PvOnOutput', 2: '/Ac/PvOnGenset'} - for pvinverter in pvinverters: - # Position will be None if PV inverter service has just been removed (after retrieving the - # service list). - position = pos.get(self._dbusmonitor.get_value(pvinverter, '/Position')) - if position is not None: - for phase in range(1, 4): - power = self._dbusmonitor.get_value(pvinverter, '/Ac/L%s/Power' % phase) - if power is not None: - path = '%s/L%s/Power' % (position, phase) - newvalues[path] = _safeadd(newvalues.get(path), power) - - current = self._dbusmonitor.get_value(pvinverter, '/Ac/L%s/Current' % phase) - if current is not None: - path = '%s/L%s/Current' % (position, phase) - newvalues[path] = _safeadd(newvalues.get(path), current) - - for path in pos.values(): - self._compute_number_of_phases(path, newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/Soc'] = self._dbusmonitor.get_value(self._batteryservice,'/Soc') - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power += self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower - inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - self._updatepvinverterspidlist() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v2.94/PageMain.qml b/FileSets/v2.94/PageMain.qml deleted file mode 100644 index ca4bd12e..00000000 --- a/FileSets/v2.94/PageMain.qml +++ /dev/null @@ -1,281 +0,0 @@ -//////// modified order to put Settings, then Notifications at top of list - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} - property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 - - model: VisualModels { -//////// put Settings at top of list - VisualItemModel { - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: settingsAtTop - } - -//////// put Notifications second - MbSubMenu { - id: menuNotificationsTop - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: !settingsAtTop - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: !settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: !settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - Component { - id: alternatorPage - PageAlternator {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - case DBusService.DBUS_SERVICE_ALTERNATOR: - page = alternatorPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v2.94/PageMain.qml.orig b/FileSets/v2.94/PageMain.qml.orig deleted file mode 100644 index a82f35c5..00000000 --- a/FileSets/v2.94/PageMain.qml.orig +++ /dev/null @@ -1,240 +0,0 @@ -import QtQuick 1.1 -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - - model: VisualModels { - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - VisualItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - Component { - id: alternatorPage - PageAlternator {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - case DBusService.DBUS_SERVICE_ALTERNATOR: - page = alternatorPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - initListView() - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v3.00/LINKS_ONLY b/FileSets/v3.00/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.00/dbus_systemcalc.py b/FileSets/v3.00/dbus_systemcalc.py deleted file mode 100755 index 96974ccc..00000000 --- a/FileSets/v3.00/dbus_systemcalc.py +++ /dev/null @@ -1,1341 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.118' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.00/dbus_systemcalc.py.orig b/FileSets/v3.00/dbus_systemcalc.py.orig deleted file mode 100755 index dd8979f8..00000000 --- a/FileSets/v3.00/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1143 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.118' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.00~32/dbus_systemcalc.py b/FileSets/v3.00~32/dbus_systemcalc.py deleted file mode 100755 index 030e8bf9..00000000 --- a/FileSets/v3.00~32/dbus_systemcalc.py +++ /dev/null @@ -1,1341 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.115' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.00~32/dbus_systemcalc.py.orig b/FileSets/v3.00~32/dbus_systemcalc.py.orig deleted file mode 100755 index f641abf8..00000000 --- a/FileSets/v3.00~32/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1143 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.115' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.01/dbus_systemcalc.py b/FileSets/v3.01/dbus_systemcalc.py deleted file mode 100755 index 523eb361..00000000 --- a/FileSets/v3.01/dbus_systemcalc.py +++ /dev/null @@ -1,1343 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.121' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.01/dbus_systemcalc.py.orig b/FileSets/v3.01/dbus_systemcalc.py.orig deleted file mode 100755 index be9f1f46..00000000 --- a/FileSets/v3.01/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1145 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.121' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.01/main.qml b/FileSets/v3.01/main.qml deleted file mode 100644 index 5034483b..00000000 --- a/FileSets/v3.01/main.qml +++ /dev/null @@ -1,582 +0,0 @@ -//////// Modified to hide the OverviewTiles page -//////// Modified to substitute flow overview pages - -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm -//////// added for GuiMods flow pages - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - -//////// modified for GuiMods pages - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" - property string currentHubOverview: "OverviewHub.qml" - property string currentMobileOverview: "" - property string currentGeneratorOverview: "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewGridParallelEnhanced page -//////// modified for OverviewHubEnhanced page - onHubOverviewTypeChanged: selectHubOverview () - - VBusItem - { - id: guiModsFlowOverview - bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" - onValueChanged: selectHubOverview () - } - -////// GuiMods — DarkMode - property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } - property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 - -////// GuiMods — DarkMode - Rectangle { - anchors - { - fill: parent - } - color: !darkMode ? "transparent" : "#202020" - z: -1 - } - - // base a new hub selection on the hub type and the enhanced flow overview flag - function selectHubOverview () - { - var newHubOverview = currentHubOverview - // Victron stock overviews with automatic selection - if (guiModsFlowOverview.value == 0) - { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - case "unknown": - newHubOverview = "OverviewHub.qml" - break; - case "Hub-4": - case "ESS": - newHubOverview = "OverviewGridParallel.qml" - break; - default: - break; - } - } - // Gui Mods simple flow - else if (guiModsFlowOverview.value === 1) - { - newHubOverview = "OverviewHubEnhanced.qml" - } - // Gui Mods complex flow (AC coupled or DC coupled) - else - { - newHubOverview = "OverviewFlowComplex.qml" - } - - if (newHubOverview != currentHubOverview) - { - replaceOverview(currentHubOverview, newHubOverview); - currentHubOverview = newHubOverview - } - - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: selectGeneratorOverview () - } - - VBusItem - { - id: generatorOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" - onValueChanged: selectGeneratorOverview () - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.generator.startstop1/AutoStartEnabled" - onValueChanged: { - extraOverview("OverviewGeneratorFp.qml", value === 1) - // Switch to FP overview in case it is the default one - if (isOverviewPage) { - pageStack.currentPage.currentIndex = getDefaultOverviewIndex() - } - } - } - function selectGeneratorOverview () - { - var newGeneratorOverview - if (generatorOverview.value === 1) - { - if (generatorOverviewEnhanced.value === 1) - newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" - else - newGeneratorOverview = "OverviewGeneratorRelay.qml" - if (currentGeneratorOverview === "") - extraOverview (newGeneratorOverview, true) - else - replaceOverview (currentGeneratorOverview, newGeneratorOverview) - currentGeneratorOverview = newGeneratorOverview - } - else - { - // hide existing generator overview if any - if (currentGeneratorOverview != "") - { - extraOverview (currentGeneratorOverview, false) - currentGeneratorOverview = "" - } - } - } - -//////// handle OverviewMobileEnhanced page - VBusItem - { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged: selectMobileOverview () - } - VBusItem - { - id: mobileOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" - onValueChanged: selectMobileOverview () - } - - // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags - function selectMobileOverview () - { - var newMobileOverview - if (mobileOverview.value === 1) - { - if (mobileOverviewEnhanced.value === 1) - newMobileOverview = "OverviewMobileEnhanced.qml" - else - newMobileOverview = "OverviewMobile.qml" - if (currentMobileOverview === "") - extraOverview (newMobileOverview, true) - else - replaceOverview (currentMobileOverview, newMobileOverview) - currentMobileOverview = newMobileOverview - } - else - { - // hide existing mobile overview if any - if (currentMobileOverview != "") - { - extraOverview (currentMobileOverview, false) - currentMobileOverview = "" - } - } - } - -//////// show/hide the OverviewTiles page - VBusItem - { - id: showOverviewTiles - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" - onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) - } - -//////// show/hide the OverviewRelays page - VBusItem { - id: showOverviewRelays - bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" - onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) - } - -//////// show/hide the Overview Tanks/Temps/Digital Inputs page - VBusItem { - id: showOverviewTanksTemps - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" - onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) - } - - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - VBusItem { - id: touchEnabled - bind: "com.victronenergy.settings/Settings/Gui/TouchEnabled" - onValueChanged: { - if (completed && value !== undefined) - toast.createToast(value ? qsTr("Touch input on") : qsTr("Touch input off"), 3000) - } - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - Connections { - target: vePlatform - onMouseRejected: toast.createToast(qsTr("Touch input disabled"), 1000) - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - -//// GuiMods - DarkMode - Row - { - spacing: 0 - anchors.fill: parent - Item { - id: pagesItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 170 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width - - MouseArea - { - anchors.fill: parent - onClicked: - { - if (darkModeItem.valid) - darkModeItem.setValue (! darkMode) - } - } - - Text - { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) - color: "white" - font.bold: true - font.pixelSize: 12 - visible: darkModeItem.valid - } - } - Item - { - id: centerScrollIndicator - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 20 - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - } - - Item { - id: menusItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: pagesItem.width - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } -//////// (commented out) -- added dynamically above -// ListElement { -// pageSource: "OverviewTiles.qml" -// } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: - { -//////// modified for OverviewGridParallelEnhanced page - selectHubOverview () - if (startWithMenu.value === 0) showOverview() - } - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - -//////// Modified to append page if oldPage not found - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - { - if (overviewModel.get(i).pageSource === oldPage) - { - overviewModel.get(i).pageSource = newPage - return - } - } - // here if oldPage wasn't found -- append the new page - overviewModel.append({"pageSource": newPage}) - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (touchEnabled.valid && !touchEnabled.value) - return - - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } - - FirmwareUpdate { id: firmwareUpdate } -} diff --git a/FileSets/v3.01/main.qml.orig b/FileSets/v3.01/main.qml.orig deleted file mode 100644 index 6251f26b..00000000 --- a/FileSets/v3.01/main.qml.orig +++ /dev/null @@ -1,388 +0,0 @@ -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - - - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewHub page - onHubOverviewTypeChanged: { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); - break; - case "Hub-4": - case "ESS": - replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); - break; - default: - break; - } - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.generator.startstop1/AutoStartEnabled" - onValueChanged: { - extraOverview("OverviewGeneratorFp.qml", value === 1) - // Switch to FP overview in case it is the default one - if (isOverviewPage) { - pageStack.currentPage.currentIndex = getDefaultOverviewIndex() - } - } - } - - VBusItem { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged:{ - extraOverview("OverviewMobile.qml", value === 1) - } - } - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - VBusItem { - id: touchEnabled - bind: "com.victronenergy.settings/Settings/Gui/TouchEnabled" - onValueChanged: { - if (completed && value !== undefined) - toast.createToast(value ? qsTr("Touch input on") : qsTr("Touch input off"), 3000) - } - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - Connections { - target: vePlatform - onMouseRejected: toast.createToast(qsTr("Touch input disabled"), 1000) - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - - Item { - anchors.verticalCenter: parent.verticalCenter - anchors.left: mbTools.left - height: mbTools.height - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - MbIcon { - id: centerScrollIndicator - - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: mbTools.verticalCenter - } - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - anchors.right: mbTools.right - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } - ListElement { - pageSource: "OverviewTiles.qml" - } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: if (startWithMenu.value === 0) showOverview() - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === oldPage) - overviewModel.get(i).pageSource = newPage - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (touchEnabled.valid && !touchEnabled.value) - return - - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } - - FirmwareUpdate { id: firmwareUpdate } -} diff --git a/FileSets/v3.10/dbus_systemcalc.py b/FileSets/v3.10/dbus_systemcalc.py deleted file mode 100755 index efa2b44b..00000000 --- a/FileSets/v3.10/dbus_systemcalc.py +++ /dev/null @@ -1,1344 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.131' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.10/dbus_systemcalc.py.orig b/FileSets/v3.10/dbus_systemcalc.py.orig deleted file mode 100755 index 724ee197..00000000 --- a/FileSets/v3.10/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1146 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.131' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.10/main.qml b/FileSets/v3.10/main.qml deleted file mode 100644 index da338007..00000000 --- a/FileSets/v3.10/main.qml +++ /dev/null @@ -1,582 +0,0 @@ -//////// Modified to hide the OverviewTiles page -//////// Modified to substitute flow overview pages - -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm -//////// added for GuiMods flow pages - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - -//////// modified for GuiMods pages - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "unknown" - property string currentHubOverview: "OverviewHub.qml" - property string currentMobileOverview: "" - property string currentGeneratorOverview: "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewGridParallelEnhanced page -//////// modified for OverviewHubEnhanced page - onHubOverviewTypeChanged: selectHubOverview () - - VBusItem - { - id: guiModsFlowOverview - bind: "com.victronenergy.settings/Settings/GuiMods/FlowOverview" - onValueChanged: selectHubOverview () - } - -////// GuiMods — DarkMode - property VBusItem darkModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/GuiMods/DarkMode" } - property bool darkMode: darkModeItem.valid && darkModeItem.value == 1 - -////// GuiMods — DarkMode - Rectangle { - anchors - { - fill: parent - } - color: !darkMode ? "transparent" : "#202020" - z: -1 - } - - // base a new hub selection on the hub type and the enhanced flow overview flag - function selectHubOverview () - { - var newHubOverview = currentHubOverview - // Victron stock overviews with automatic selection - if (guiModsFlowOverview.value == 0) - { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - case "unknown": - newHubOverview = "OverviewHub.qml" - break; - case "Hub-4": - case "ESS": - newHubOverview = "OverviewGridParallel.qml" - break; - default: - break; - } - } - // Gui Mods simple flow - else if (guiModsFlowOverview.value === 1) - { - newHubOverview = "OverviewHubEnhanced.qml" - } - // Gui Mods complex flow (AC coupled or DC coupled) - else - { - newHubOverview = "OverviewFlowComplex.qml" - } - - if (newHubOverview != currentHubOverview) - { - replaceOverview(currentHubOverview, newHubOverview); - currentHubOverview = newHubOverview - } - - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: selectGeneratorOverview () - } - - VBusItem - { - id: generatorOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview" - onValueChanged: selectGeneratorOverview () - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.generator.startstop1/GensetProductId" - onValueChanged: { - extraOverview("OverviewGeneratorFp.qml", value === 0xB040) - // Switch to FP overview in case it is the default one - if (isOverviewPage) { - pageStack.currentPage.currentIndex = getDefaultOverviewIndex() - } - } - } - function selectGeneratorOverview () - { - var newGeneratorOverview - if (generatorOverview.value === 1) - { - if (generatorOverviewEnhanced.value === 1) - newGeneratorOverview = "OverviewGeneratorRelayEnhanced.qml" - else - newGeneratorOverview = "OverviewGeneratorRelay.qml" - if (currentGeneratorOverview === "") - extraOverview (newGeneratorOverview, true) - else - replaceOverview (currentGeneratorOverview, newGeneratorOverview) - currentGeneratorOverview = newGeneratorOverview - } - else - { - // hide existing generator overview if any - if (currentGeneratorOverview != "") - { - extraOverview (currentGeneratorOverview, false) - currentGeneratorOverview = "" - } - } - } - -//////// handle OverviewMobileEnhanced page - VBusItem - { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged: selectMobileOverview () - } - VBusItem - { - id: mobileOverviewEnhanced - bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedMobileOverview" - onValueChanged: selectMobileOverview () - } - - // base a new mobile overview selection on the the mobile overview and enhanced mobile overview flags - function selectMobileOverview () - { - var newMobileOverview - if (mobileOverview.value === 1) - { - if (mobileOverviewEnhanced.value === 1) - newMobileOverview = "OverviewMobileEnhanced.qml" - else - newMobileOverview = "OverviewMobile.qml" - if (currentMobileOverview === "") - extraOverview (newMobileOverview, true) - else - replaceOverview (currentMobileOverview, newMobileOverview) - currentMobileOverview = newMobileOverview - } - else - { - // hide existing mobile overview if any - if (currentMobileOverview != "") - { - extraOverview (currentMobileOverview, false) - currentMobileOverview = "" - } - } - } - -//////// show/hide the OverviewTiles page - VBusItem - { - id: showOverviewTiles - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTileOverview" - onValueChanged: extraOverview ("OverviewTiles.qml", value === 1) - } - -//////// show/hide the OverviewRelays page - VBusItem { - id: showOverviewRelays - bind: "com.victronenergy.settings/Settings/GuiMods/ShowRelayOverview" - onValueChanged: extraOverview ("OverviewRelays.qml", value === 1) - } - -//////// show/hide the Overview Tanks/Temps/Digital Inputs page - VBusItem { - id: showOverviewTanksTemps - bind: "com.victronenergy.settings/Settings/GuiMods/ShowTanksTempsDigIn" - onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1) - } - - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - VBusItem { - id: touchEnabled - bind: "com.victronenergy.settings/Settings/Gui/TouchEnabled" - onValueChanged: { - if (completed && value !== undefined) - toast.createToast(value ? qsTr("Touch input on") : qsTr("Touch input off"), 3000) - } - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - Connections { - target: vePlatform - onMouseRejected: toast.createToast(qsTr("Touch input disabled"), 1000) - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - -//// GuiMods - DarkMode - Row - { - spacing: 0 - anchors.fill: parent - Item { - id: pagesItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 170 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width - - MouseArea - { - anchors.fill: parent - onClicked: - { - if (darkModeItem.valid) - darkModeItem.setValue (! darkMode) - } - } - - Text - { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode")) - color: "white" - font.bold: true - font.pixelSize: 12 - visible: darkModeItem.valid - } - } - Item - { - id: centerScrollIndicator - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: 20 - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - } - - Item { - id: menusItem - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - width: pagesItem.width - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } -//////// (commented out) -- added dynamically above -// ListElement { -// pageSource: "OverviewTiles.qml" -// } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: - { -//////// modified for OverviewGridParallelEnhanced page - selectHubOverview () - if (startWithMenu.value === 0) showOverview() - } - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - -//////// Modified to append page if oldPage not found - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - { - if (overviewModel.get(i).pageSource === oldPage) - { - overviewModel.get(i).pageSource = newPage - return - } - } - // here if oldPage wasn't found -- append the new page - overviewModel.append({"pageSource": newPage}) - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (touchEnabled.valid && !touchEnabled.value) - return - - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } - - FirmwareUpdate { id: firmwareUpdate } -} diff --git a/FileSets/v3.10/main.qml.orig b/FileSets/v3.10/main.qml.orig deleted file mode 100644 index 27b228b6..00000000 --- a/FileSets/v3.10/main.qml.orig +++ /dev/null @@ -1,388 +0,0 @@ -import QtQuick 1.1 - -import Qt.labs.components.native 1.0 -import com.victron.velib 1.0 -import "utils.js" as Utils - -PageStackWindow { - id: rootWindow - - gpsConnected: gpsFix.value === 1 - onCompletedChanged: checkAlarm() - initialPage: PageMain {} - - property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" } - property VeQuickItem gpsFix: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Fix") } - property bool completed: false - property bool showAlert: NotificationCenter.alert - property bool alarm: NotificationCenter.alarm - property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid - property string bindPrefix: "com.victronenergy.settings" - - property bool isNotificationPage: pageStack.currentPage && pageStack.currentPage.title === qsTr("Notifications") - property bool isOverviewPage: pageStack.currentPage && pageStack.currentPage.model === overviewModel; - property bool isOfflineFwUpdatePage: pageStack.currentPage && pageStack.currentPage.objectName === "offlineFwUpdatePage"; - - - property string hubOverviewType: theSystem.systemType.valid ? - withoutGridMeter.value === 1 ? "Hub" : theSystem.systemType.value : "" - - // Keep track of the current view (menu/overview) to show as default next time the - // CCGX is restarted - onIsOverviewPageChanged: startWithMenu.setValue(isOverviewPage ? 0 : 1) - - // Add the correct OverviewHub page - onHubOverviewTypeChanged: { - switch(hubOverviewType){ - case "Hub": - case "Hub-1": - case "Hub-2": - case "Hub-3": - replaceOverview("OverviewGridParallel.qml", "OverviewHub.qml"); - break; - case "Hub-4": - case "ESS": - replaceOverview("OverviewHub.qml", "OverviewGridParallel.qml"); - break; - default: - break; - } - // Workaround the QTBUG-17012 (only the first sentence in each case of Switch Statement can be executed) - // by adding a return statement - return - } - - VBusItem { - id: generatorOverview - bind: "com.victronenergy.settings/Settings/Relay/Function" - onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1) - } - - VBusItem { - id: fischerPandaGenOverview - bind: "com.victronenergy.generator.startstop1/GensetProductId" - onValueChanged: { - extraOverview("OverviewGeneratorFp.qml", value === 0xB040) - // Switch to FP overview in case it is the default one - if (isOverviewPage) { - pageStack.currentPage.currentIndex = getDefaultOverviewIndex() - } - } - } - - VBusItem { - id: mobileOverview - bind: "com.victronenergy.settings/Settings/Gui/MobileOverview" - onValueChanged:{ - extraOverview("OverviewMobile.qml", value === 1) - } - } - VBusItem { - id: tanksOverview - bind: "com.victronenergy.settings/Settings/Gui/TanksOverview" - onValueChanged:{ - extraOverview("OverviewTanks.qml", value === 1) - } - } - - VBusItem { - id: startWithMenu - bind: "com.victronenergy.settings/Settings/Gui/StartWithMenuView" - } - - VBusItem { - id: withoutGridMeter - bind: "com.victronenergy.settings/Settings/CGwacs/RunWithoutGridMeter" - } - - - VBusItem { - id: defaultOverview - bind: "com.victronenergy.settings/Settings/Gui/DefaultOverview" - } - - VBusItem { - id: touchEnabled - bind: "com.victronenergy.settings/Settings/Gui/TouchEnabled" - onValueChanged: { - if (completed && value !== undefined) - toast.createToast(value ? qsTr("Touch input on") : qsTr("Touch input off"), 3000) - } - } - - // Note: finding a firmware image on the storage device is error 4 for vrm storage - // since it should not be used for logging. That fact is used here to determine if - // there is a firmware image. - Connections { - target: storageEvents - onVrmStorageError: { - if (error === 4) { - setTopPage(offlineFwUpdates) - } - } - } - - Connections { - target: vePlatform - onMouseRejected: toast.createToast(qsTr("Touch input disabled"), 1000) - } - - onAlarmChanged: { - if (completed) - checkAlarm() - } - - // always keep track of system information - HubData { - id: theSystem - } - - // note: used for leaving the overviews as well - function backToMainMenu() - { - pageStack.pop(initialPage); - } - - Toast { - id: toast - transform: Scale { - xScale: screen.scaleX - yScale: screen.scaleY - origin.x: toast.width / 2 - origin.y: toast.height / 2 - } - } - - SignalToaster {} - - ToolbarHandlerPages { - id: mainToolbarHandler - isDefault: true - } - - ToolBarLayout { - id: mbTools - height: parent.height - - Item { - anchors.verticalCenter: parent.verticalCenter - anchors.left: mbTools.left - height: mbTools.height - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.leftAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - anchors.verticalCenter: parent.verticalCenter - iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : "" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: pageStack.currentPage ? pageStack.currentPage.leftText : "" - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - - MbIcon { - id: centerScrollIndicator - - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: mbTools.verticalCenter - } - iconId: pageStack.currentPage ? pageStack.currentPage.scrollIndicator : "" - } - - Item { - anchors.verticalCenter: parent.verticalCenter - height: mbTools.height - anchors.right: mbTools.right - width: 200 - - MouseArea { - anchors.fill: parent - onClicked: { - if (pageStack.currentPage) - pageStack.currentPage.toolbarHandler.rightAction(true) - } - } - - Row { - anchors.centerIn: parent - - MbIcon { - iconId: pageStack.currentPage ? pageStack.currentPage.rightIcon : "" - anchors.verticalCenter: parent.verticalCenter - } - - Text { - text: pageStack.currentPage ? pageStack.currentPage.rightText : "" - anchors.verticalCenter: parent.verticalCenter - color: "white" - font.bold: true - font.pixelSize: 16 - } - } - } - } - - Component.onCompleted: { - completed = true - } - - ListModel { - id: overviewModel - ListElement { - pageSource: "OverviewHub.qml" - } - ListElement { - pageSource: "OverviewTiles.qml" - } - } - - Component { - id: overviewComponent - PageFlow { - // Display default overview when loaded - defaultIndex: getDefaultOverviewIndex() - // Store the current overview page as default - onCurrentIndexChanged: if (active) defaultOverview.setValue(overviewModel.get(currentIndex).pageSource.replace(".qml", "")) - model: overviewModel - } - } - - // When all the related settings items are valid, show the overview page if was the last oppened page - // before restarting - Timer { - interval: 2000 - running: completed && overviewsLoaded && startWithMenu.valid - onTriggered: if (startWithMenu.value === 0) showOverview() - } - - function getDefaultOverviewIndex() - { - if(!defaultOverview.valid) - return 0 - for (var i = 0; i < overviewModel.count; i++){ - if (overviewModel.get(i).pageSource.replace(".qml", "") === defaultOverview.value) { - return i - } - } - return 0 - } - - Component { - id: noticationsComponent - PageNotifications {} - } - - Component { - id: offlineFwUpdates - PageSettingsFirmwareOffline { checkOnCompleted: true} - } - - // Add or remove extra overviews. for example, generator overview - // shouldn't be shown if the start/stop functionality is not enabled. - // Index parameter is optional, usefull to keep an order. - function extraOverview(name, show, index) - { - var i = 0 - if (show) { - if (index !== undefined) { - if (overviewModel.get(index).pageSource === name) - return - // First append the page - overviewModel.append({"pageSource": name}) - // Then move all the pages behind index - overviewModel.move(index, overviewModel.count - 2, overviewModel.count - 2) - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - // Don't append if already exists - return - overviewModel.append({"pageSource": name}) - } - } else { - for (i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === name) - overviewModel.remove(i) - } - } - - function replaceOverview(oldPage, newPage) - { - for (var i = 0; i < overviewModel.count; i++) - if (overviewModel.get(i).pageSource === oldPage) - overviewModel.get(i).pageSource = newPage - } - - // Central mover for the ball animation on the overviews - // Instead of using a timer per line, using a central one - // reduces the CPU usage a little bit and makes the animations - // smoother. - Timer { - id: mover - property double pos: _counter / _loops - property int _counter - property int _loops: 13 - - interval: 100 - running: true - repeat: true - onTriggered: if (_counter >= (_loops - 1)) _counter = 0; else _counter++ - } - - // If an overview or notifications is active, the new page will replace it - // instead to be pushed. This way we prevent an unwanted stackpage depth - // increment everytime another page wants to be on top. - function setTopPage(page) - { - if (touchEnabled.valid && !touchEnabled.value) - return - - if (isNotificationPage || isOverviewPage || isOfflineFwUpdatePage) - rootWindow.pageStack.replace(page); - else - rootWindow.pageStack.push(page); - } - - function spuriousKeyPress() - { - return !pageStack.currentPage || !pageStack.currentPage.active - } - - function showOverview() - { - if (spuriousKeyPress() || isOverviewPage) - return - setTopPage(overviewComponent) - } - - function showPageNotifications() - { - if (spuriousKeyPress() || isNotificationPage) - return - setTopPage(noticationsComponent) - } - - function checkAlarm() - { - if (alarm) - showPageNotifications() - } - - FirmwareUpdate { id: firmwareUpdate } -} diff --git a/FileSets/v3.12/LINKS_ONLY b/FileSets/v3.12/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.12/dbus_systemcalc.py b/FileSets/v3.12/dbus_systemcalc.py deleted file mode 100755 index 46010175..00000000 --- a/FileSets/v3.12/dbus_systemcalc.py +++ /dev/null @@ -1,1344 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.134' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, -#### add for GuiMods - '/Ac/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.12/dbus_systemcalc.py.orig b/FileSets/v3.12/dbus_systemcalc.py.orig deleted file mode 100755 index 9cead383..00000000 --- a/FileSets/v3.12/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1146 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.134' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - try: - c = _safeadd(c, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - p = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/P' % (active_input + 1, phase)) - mc = self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/%d/%s/I' % (active_input + 1, phase)) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.14/PageMain.qml b/FileSets/v3.14/PageMain.qml deleted file mode 100644 index fc1446a7..00000000 --- a/FileSets/v3.14/PageMain.qml +++ /dev/null @@ -1,280 +0,0 @@ -//////// modified order to put Settings, then Notifications at top of list - -import QtQuick 1.1 -import "utils.js" as Utils -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - property VBusItem moveSettings: VBusItem { id: moveSettings; bind: Utils.path("com.victronenergy.settings", "/Settings/GuiMods/MoveSettings")} - property bool settingsAtTop: moveSettings.valid && moveSettings.value === 1 - - model: VisualModels { -//////// put Settings at top of list - VisibleItemModel { - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: settingsAtTop - } - -//////// put Notifications second - MbSubMenu { - id: menuNotificationsTop - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - - VisibleItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - show: !settingsAtTop - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - show: !settingsAtTop - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: !settingsAtTop && deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - Component { - id: alternatorPage - PageAlternator {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - case DBusService.DBUS_SERVICE_ALTERNATOR: - page = alternatorPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v3.14/PageMain.qml.orig b/FileSets/v3.14/PageMain.qml.orig deleted file mode 100644 index 66126a8f..00000000 --- a/FileSets/v3.14/PageMain.qml.orig +++ /dev/null @@ -1,239 +0,0 @@ -import QtQuick 1.1 -import com.victron.velib 1.0 - -MbPage { - id: root - title: qsTr("Device List") - - model: VisualModels { - VisualDataModel { - model: VeSortFilterProxyModel { - model: DeviceList { - id: deviceList - onRowsAboutToBeRemoved: { - for (var i = first; i <= last; i++) - deviceList.page(i).destroy() - } - } - sortRole: DeviceList.DescriptionRole - dynamicSortFilter: true - naturalSort: true - sortCaseSensitivity: Qt.CaseInsensitive - } - - delegate: MbDevice { - iconId: "icon-toolbar-enter" - service: model.page.service - subpage: model.page - } - } - VisibleItemModel { - MbSubMenu { - id: menuNotifications - description: qsTr("Notifications") - item: VBusItem { - property variant active: NotificationCenter.notifications.filter( - function isActive(obj) { return obj.active} ) - value: active.length > 0 ? active.length : "" - } - subpage: Component { PageNotifications {} } - } - - MbSubMenu { - description: qsTr("Settings") - subpage: Component { PageSettings {} } - } - - MbOK { - description: qsTr("Remove disconnected devices") - value: qsTr("Press to remove") - show: deviceList.disconnectedDevices != 0 - editable: true - - function clicked() { - listview.decrementCurrentIndex() - deviceList.removeDisconnected() - } - } - } - } - - Component { - id: vebusPage - PageVebus {} - } - - Component { - id: multiRsPage - PageMultiRs {} - } - - Component { - id: batteryPage - PageBattery {} - } - - Component { - id: solarChargerPage - PageSolarCharger {} - } - - Component { - id: acInPage - PageAcIn {} - } - - Component { - id: acChargerPage - PageAcCharger {} - } - - Component { - id: tankPage - PageTankSensor {} - } - - Component { - id: motorDrivePage - PageMotorDrive {} - } - - Component { - id: inverterPage - PageInverter {} - } - - Component { - id: pulseCounterPage - PagePulseCounter {} - } - - Component { - id: digitalInputPage - PageDigitalInput {} - } - - Component { - id: temperatureSensorPage - PageTemperatureSensor {} - } - - Component { - id: unsupportedDevicePage - PageUnsupportedDevice {} - } - - Component { - id: meteoDevicePage - PageMeteo {} - } - - Component { - id: evChargerPage - PageEvCharger {} - } - - Component { - id: dcMeterPage - PageDcMeter {} - } - - Component { - id: alternatorPage - PageAlternator {} - } - - function addService(service) - { - var name = service.name - - var page - switch(service.type) - { - case DBusService.DBUS_SERVICE_MULTI: - page = vebusPage - break; - case DBusService.DBUS_SERVICE_MULTI_RS: - page = multiRsPage - break; - case DBusService.DBUS_SERVICE_BATTERY: - page = batteryPage - break; - case DBusService.DBUS_SERVICE_SOLAR_CHARGER: - page = solarChargerPage - break; - case DBusService.DBUS_SERVICE_PV_INVERTER: - page = acInPage - break; - case DBusService.DBUS_SERVICE_AC_CHARGER: - page = acChargerPage - break; - case DBusService.DBUS_SERVICE_TANK: - page = tankPage - break; - case DBusService.DBUS_SERVICE_GRIDMETER: - page = acInPage - break - case DBusService.DBUS_SERVICE_GENSET: - page = acInPage - break - case DBusService.DBUS_SERVICE_MOTOR_DRIVE: - page = motorDrivePage - break - case DBusService.DBUS_SERVICE_INVERTER: - page = inverterPage - break; - case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR: - page = temperatureSensorPage - break; - case DBusService.DBUS_SERVICE_SYSTEM_CALC: - return; - case DBusService.DBUS_SERVICE_DIGITAL_INPUT: - page = digitalInputPage - break; - case DBusService.DBUS_SERVICE_PULSE_COUNTER: - page = pulseCounterPage - break; - case DBusService.DBUS_SERVICE_UNSUPPORTED: - page = unsupportedDevicePage - break; - case DBusService.DBUS_SERVICE_METEO: - page = meteoDevicePage - break; - case DBusService.DBUS_SERVICE_VECAN: - return; - case DBusService.DBUS_SERVICE_EVCHARGER: - page = evChargerPage - break - case DBusService.DBUS_SERVICE_ACLOAD: - page = acInPage - break - case DBusService.DBUS_SERVICE_HUB4: - return; - case DBusService.DBUS_SERVICE_FUELCELL: - case DBusService.DBUS_SERVICE_DCSOURCE: - case DBusService.DBUS_SERVICE_DCLOAD: - case DBusService.DBUS_SERVICE_DCSYSTEM: - page = dcMeterPage - break - case DBusService.DBUS_SERVICE_ALTERNATOR: - page = alternatorPage - break - default: - console.log("unknown service " + name) - return; - } - - deviceList.append(service, page.createObject(root, {service: service, bindPrefix: service.name})) - } - - Component.onCompleted: { - for (var i = 0; i < DBusServices.count; i++) - addService(DBusServices.at(i)) - } - - Connections { - target: DBusServices - onDbusServiceFound: addService(service) - } -} diff --git a/FileSets/v3.22/dbus_systemcalc.py b/FileSets/v3.22/dbus_systemcalc.py deleted file mode 100755 index 900b0efc..00000000 --- a/FileSets/v3.22/dbus_systemcalc.py +++ /dev/null @@ -1,1370 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.150' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.22/dbus_systemcalc.py.orig b/FileSets/v3.22/dbus_systemcalc.py.orig deleted file mode 100755 index 6ae68254..00000000 --- a/FileSets/v3.22/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1172 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.150' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~1/LINKS_ONLY b/FileSets/v3.40~1/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~1/dbus_systemcalc.py b/FileSets/v3.40~1/dbus_systemcalc.py deleted file mode 100755 index 761ffb18..00000000 --- a/FileSets/v3.40~1/dbus_systemcalc.py +++ /dev/null @@ -1,1371 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.158' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - #delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - #### continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % phase] = p - newvalues['/Ac/ActiveIn/%s/Current' % phase] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - # u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, voltageOut[phase]): - ac_out = i * voltageOut[phase] - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~1/dbus_systemcalc.py.orig b/FileSets/v3.40~1/dbus_systemcalc.py.orig deleted file mode 100755 index a034e56f..00000000 --- a/FileSets/v3.40~1/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1173 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.158' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.DynamicEss()] - #delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1]} - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': '%s'}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ==== Vebus ==== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - if multi_path is not None: - dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current') - newvalues['/Dc/Vebus/Current'] = dc_current - dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power') - # Just in case /Dc/0/Power is not available - if dc_power == None and dc_current is not None: - dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage') - if dc_voltage is not None: - dc_power = dc_voltage * dc_current - # Note that there is also vebuspower, which is the total DC power summed over all multis. - # However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense - # to add the Dc currents of all multis if they do not share the same DC voltage. - newvalues['/Dc/Vebus/Power'] = dc_power - - # ===== AC IN SOURCE ===== - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - if path == '/Dc/Battery/State': - state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'} - return state[value] - item = self._summeditems.get(path) - if item is not None: - return item['gettext'] % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~10/dbus_systemcalc.py b/FileSets/v3.40~10/dbus_systemcalc.py deleted file mode 100755 index 80bf657f..00000000 --- a/FileSets/v3.40~10/dbus_systemcalc.py +++ /dev/null @@ -1,1488 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.162' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~13/dbus_systemcalc.py b/FileSets/v3.40~13/dbus_systemcalc.py deleted file mode 100755 index 472e5b49..00000000 --- a/FileSets/v3.40~13/dbus_systemcalc.py +++ /dev/null @@ -1,1489 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.166' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~13/dbus_systemcalc.py.orig b/FileSets/v3.40~13/dbus_systemcalc.py.orig deleted file mode 100755 index 1d794b06..00000000 --- a/FileSets/v3.40~13/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1246 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.166' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~15/LINKS_ONLY b/FileSets/v3.40~15/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~16/LINKS_ONLY b/FileSets/v3.40~16/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~16/dbus_systemcalc.py b/FileSets/v3.40~16/dbus_systemcalc.py deleted file mode 100755 index 25b07515..00000000 --- a/FileSets/v3.40~16/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.173' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~16/dbus_systemcalc.py.orig b/FileSets/v3.40~16/dbus_systemcalc.py.orig deleted file mode 100755 index 1bc8fbc0..00000000 --- a/FileSets/v3.40~16/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.173' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~2/LINKS_ONLY b/FileSets/v3.40~2/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~20/LINKS_ONLY b/FileSets/v3.40~20/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~20/dbus_systemcalc.py b/FileSets/v3.40~20/dbus_systemcalc.py deleted file mode 100755 index febb4f05..00000000 --- a/FileSets/v3.40~20/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.174' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~20/dbus_systemcalc.py.orig b/FileSets/v3.40~20/dbus_systemcalc.py.orig deleted file mode 100755 index ccfba7ab..00000000 --- a/FileSets/v3.40~20/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.174' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~27/LINKS_ONLY b/FileSets/v3.40~27/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~27/dbus_systemcalc.py b/FileSets/v3.40~27/dbus_systemcalc.py deleted file mode 100755 index 673aac57..00000000 --- a/FileSets/v3.40~27/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.175' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~27/dbus_systemcalc.py.orig b/FileSets/v3.40~27/dbus_systemcalc.py.orig deleted file mode 100755 index eb18ecbd..00000000 --- a/FileSets/v3.40~27/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.175' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~29/LINKS_ONLY b/FileSets/v3.40~29/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~29/dbus_systemcalc.py b/FileSets/v3.40~29/dbus_systemcalc.py deleted file mode 100755 index fbceb5bd..00000000 --- a/FileSets/v3.40~29/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.177' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~29/dbus_systemcalc.py.orig b/FileSets/v3.40~29/dbus_systemcalc.py.orig deleted file mode 100755 index 6b403f6b..00000000 --- a/FileSets/v3.40~29/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.177' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~30/LINKS_ONLY b/FileSets/v3.40~30/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~30/dbus_systemcalc.py b/FileSets/v3.40~30/dbus_systemcalc.py deleted file mode 100755 index 8b3b6470..00000000 --- a/FileSets/v3.40~30/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.178' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~30/dbus_systemcalc.py.orig b/FileSets/v3.40~30/dbus_systemcalc.py.orig deleted file mode 100755 index 3c2bf316..00000000 --- a/FileSets/v3.40~30/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.178' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~33/LINKS_ONLY b/FileSets/v3.40~33/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~33/dbus_systemcalc.py b/FileSets/v3.40~33/dbus_systemcalc.py deleted file mode 100755 index dca4bcd8..00000000 --- a/FileSets/v3.40~33/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.179' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~33/dbus_systemcalc.py.orig b/FileSets/v3.40~33/dbus_systemcalc.py.orig deleted file mode 100755 index 01f39423..00000000 --- a/FileSets/v3.40~33/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.179' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~34/LINKS_ONLY b/FileSets/v3.40~34/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~34/dbus_systemcalc.py b/FileSets/v3.40~34/dbus_systemcalc.py deleted file mode 100755 index abed7a5c..00000000 --- a/FileSets/v3.40~34/dbus_systemcalc.py +++ /dev/null @@ -1,1500 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.181' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] -#### changed for GuiMods - include wind and motor drive - #### was newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~34/dbus_systemcalc.py.orig b/FileSets/v3.40~34/dbus_systemcalc.py.orig deleted file mode 100755 index f75eb1fe..00000000 --- a/FileSets/v3.40~34/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1256 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.181' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy, - '/Dc/0/Current': dummy, - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1], - 'acin0min': ['/Settings/Gui/Gauges/Ac/In/0/Current/Min', float(0), -float("inf"), 0], - 'acin1min': ['/Settings/Gui/Gauges/Ac/In/1/Current/Min', float(0), -float("inf"), 0], - 'acin0max': ['/Settings/Gui/Gauges/Ac/In/0/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/In/1/Current/Max', float(0), 0, float("inf")], - 'dcinmax': ['/Settings/Gui/Gauges/Dc/Input/Power/Max', float(0), 0, float("inf")], - 'dcsysmax': ['/Settings/Gui/Gauges/Dc/System/Power/Max', float(0), 0, float("inf")], - 'pvmax': ['/Settings/Gui/Gauges/Pv/Power/Max', float(0), 0, float("inf")], - 'noacinmax': ['/Settings/Gui/Gauges/Ac/NoAcIn/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin1max': ['/Settings/Gui/Gauges/Ac/AcIn1/Consumption/Current/Max', float(0), 0, float("inf")], - 'acin2max': ['/Settings/Gui/Gauges/Ac/AcIn2/Consumption/Current/Max', float(0), 0, float("inf")], - } - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/Current': {'gettext': '%.1F A'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. Instances of 'lynxparallel' are preferred over regular BMSes. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), - not s.startswith('com.victronenergy.battery.lynxparallel'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:3])[0][3] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - newvalues['/Dc/System/Current'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - newvalues['/Dc/System/Current'] = _safeadd(newvalues['/Dc/System/Current'], - self._dbusmonitor.get_value(meter, '/Dc/0/Current')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + alternator_power + vebuspower + inverter_power - battery_power - try: - newvalues['/Dc/System/Current'] = \ - newvalues['/Dc/System/Power'] / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - try: - newvalues['/Dc/System/Current'] = \ - solarchargers_loadoutput_power / newvalues['/Dc/Battery/Voltage'] - except (KeyError, ZeroDivisionError): - pass - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - if (self._settings['gaugeautomax']): - # min/max values are stored and updated in localsettings - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Gauge limits are updated automatically and stored in localsettings - # 0-> Manual: Gauge limits are entered manually by the user - # The gui pulls the gauge limits from localsettings and provides - # a means for the user to set them if Automax is off. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'acin1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'acin2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'noacin' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - activeInNr = int(activeIn[-1]) -1 if activeIn != 'noacin' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - self._settings['acin%smin' % activeInNr] = min(0, - self._settings['acin%smin' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - self._settings['acin%smax' % activeInNr] = max(self._settings['acin%smax' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._settings['%smax' % activeIn] = max(self._settings['%smax' % activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - # DC input - self._settings['dcinmax'] = max(self._settings['dcinmax'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - self._settings['dcsysmax'] = _safemax(self._settings['dcsysmax'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - self._settings['pvmax'] = _safemax(self._settings['pvmax'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~6/LINKS_ONLY b/FileSets/v3.40~6/LINKS_ONLY new file mode 100644 index 00000000..e69de29b diff --git a/FileSets/v3.40~6/dbus_systemcalc.py b/FileSets/v3.40~6/dbus_systemcalc.py deleted file mode 100755 index 504106b6..00000000 --- a/FileSets/v3.40~6/dbus_systemcalc.py +++ /dev/null @@ -1,1488 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -#### modified for GuiMods - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.160' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L1/F': dummy, - '/Ac/Out/L2/F': dummy, - '/Ac/Out/L3/F': dummy, - '/Ac/ActiveIn/L1/V': dummy, - '/Ac/ActiveIn/L2/V': dummy, - '/Ac/ActiveIn/L3/V': dummy, - '/Ac/ActiveIn/L1/F': dummy, - '/Ac/ActiveIn/L2/F': dummy, - '/Ac/ActiveIn/L3/F': dummy, - - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, -#### add for GuiMods - '/Ac/L1/Voltage': dummy, - '/Ac/L2/Voltage': dummy, - '/Ac/L3/Voltage': dummy, - '/Ac/L1/Frequency': dummy, - '/Ac/L2/Frequency': dummy, - '/Ac/L3/Frequency': dummy, - - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, -#### add for GuiMods - '/Ac/Out/L1/F': dummy, - - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - }, -#### added for GuiMods - 'com.victronenergy.dcsource': { - '/Dc/0/Power': dummy, - '/Settings/MonitorMode': dummy - }, - 'com.victronenergy.motordrive': - { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/Vebus/Current': {'gettext': '%.1F A'}, - '/Dc/Vebus/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'}, -#### added for GuiMods - '/Dc/WindGenerator/Power': {'gettext': '%.0F W'}, - '/Dc/MotorDrive/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Grid/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Genset/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Genset/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnOutput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnOutput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ConsumptionOnInput/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ConsumptionOnInput/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/Consumption/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/Consumption/Frequency': {'gettext': '%.1F Hz'}, - '/Ac/ActiveIn/L1/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L2/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/L3/Voltage': {'gettext': '%.1F V'}, - '/Ac/ActiveIn/Frequency': {'gettext': '%.1F Hz'}, - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - -#### added for GuiMods - self.dcSystemPower = [0, 0, 0] - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: -#### modified for GuiMods - # some alternators do not provide a valid power value if not running - # or below a minimum power/current - # so fill in a zero power so that the systemcalc power becomes valid - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - p = 0 - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - -#### added for GuiMods - # ==== MOTOR DRIVE ==== - motordrives = self._dbusmonitor.get_service_list('com.victronenergy.motordrive') - for motordrive in motordrives: - p = self._dbusmonitor.get_value(motordrive, '/Dc/0/Power') - if p is None: - p = 0 - - if '/Dc/MotorDrive/Power' not in newvalues: - newvalues['/Dc/MotorDrive/Power'] = p - else: - newvalues['/Dc/MotorDrive/Power'] += p - -#### added for GuiMods - # ==== DC SOURCES ==== - dcSources = self._dbusmonitor.get_service_list('com.victronenergy.dcsource') - for dcSource in dcSources: - monitorMode = self._dbusmonitor.get_value(dcSource,'/Settings/MonitorMode') - # ==== WIND GENERATOR ==== - if monitorMode == -8: - p = self._dbusmonitor.get_value(dcSource, '/Dc/0/Power') - if p is None: - continue - if '/Dc/WindGenerator/Power' not in newvalues: - newvalues['/Dc/WindGenerator/Power'] = p - else: - newvalues['/Dc/WindGenerator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) -#### added for GuiMods - windgen_power = newvalues.get('/Dc/WindGenerator/Power', 0) - motordrive_power = newvalues.get('/Dc/MotorDrive/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power -#### changed for GuiMods - # average DC system power over 3 passes (seconds) to minimize wild swings in displayed value - self.dcSystemPower[2] = self.dcSystemPower[1] - self.dcSystemPower[1] = self.dcSystemPower[0] - self.dcSystemPower[0] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power + alternator_power + windgen_power - motordrive_power - newvalues['/Dc/System/Power'] = (self.dcSystemPower[0] + self.dcSystemPower[1] + self.dcSystemPower[2]) / 3 - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - -#### added for GuiMods - voltageIn = { "L1" : None, "L2" : None, "L3" : None } - voltageOut = { "L1" : None, "L2" : None, "L3" : None } - frequencyIn = None - frequencyOut = None - - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/%s/Voltage' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/%s/Frequency' % phase) - - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(em.service, '/Ac/ActiveIn/%s/F' % phase) - - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input+1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input+1, phase)) - - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/V' % phase) - if frequencyIn == None: - freq = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/F' % phase) - if freq != None: - frequencyIn = freq - - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) -#### added for GuiMods - if voltageIn[phase] == None: - voltageIn[phase] = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/V' % (active_input + 1, phase)) - if frequencyIn == None: - frequencyIn = self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/F' % (active_input + 1, phase)) - - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/%s/%s/Voltage' % (device_type, phase)] = voltageIn[phase] - newvalues['/Ac/%s/Frequency' % (device_type)] = frequencyIn - - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc -#### added for GuiMods - if p != None: - newvalues['/Ac/ActiveIn/%s/Voltage' % (phase,)] = voltageIn[phase] - newvalues['/Ac/ActiveIn/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = newvalues.get('/Ac/PvOnOutput/%s/Voltage' % phase) - if frequencyOut == None: - frequencyOut = newvalues.get('/Ac/PvOnOutput/%s/Frequency' % phase) - - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/F' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: -#### modified for GuiMods - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) -#### added for GuiMods - if voltageOut[phase] == None: - voltageOut[phase] = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/V' % phase) - if frequencyOut == None: - frequencyOut = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/F' % phase) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) -#### added for GuiMods - newvalues['/Ac/ConsumptionOnOutput/%s/Voltage' % phase] = voltageOut[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Voltage' % phase] = voltageIn[phase] - if voltageOut[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageOut[phase] - elif voltageIn[phase] != None: - newvalues['/Ac/Consumption/%s/Voltage' % phase] = voltageIn[phase] - if frequencyIn != None: - newvalues['/Ac/ConsumptionOnInput/Frequency'] = frequencyIn - if frequencyOut != None: - newvalues['/Ac/ConsumptionOnOutput/Frequency'] = frequencyOut - if frequencyOut != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyOut - elif frequencyIn != None: - newvalues['/Ac/Consumption/Frequency'] = frequencyIn - - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/FileSets/v3.40~6/dbus_systemcalc.py.orig b/FileSets/v3.40~6/dbus_systemcalc.py.orig deleted file mode 100755 index 0c4fe3e1..00000000 --- a/FileSets/v3.40~6/dbus_systemcalc.py.orig +++ /dev/null @@ -1,1287 +0,0 @@ -#!/usr/bin/python3 -u -# -*- coding: utf-8 -*- - -from dbus.mainloop.glib import DBusGMainLoop -import dbus -import argparse -import sys -import os -import json -import time -import re -from gi.repository import GLib - -# Victron packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python')) -from vedbus import VeDbusService -from ve_utils import get_vrm_portal_id, exit_on_error -from dbusmonitor import DbusMonitor -from settingsdevice import SettingsDevice -from logger import setup_logging -import delegates -from sc_utils import safeadd as _safeadd, safemax as _safemax - -softwareVersion = '2.160' - -class SystemCalc: - STATE_IDLE = 0 - STATE_CHARGING = 1 - STATE_DISCHARGING = 2 - BATSERVICE_DEFAULT = 'default' - BATSERVICE_NOBATTERY = 'nobattery' - def __init__(self): - # Why this dummy? Because DbusMonitor expects these values to be there, even though we don't - # need them. So just add some dummy data. This can go away when DbusMonitor is more generic. - dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} - dbus_tree = { - 'com.victronenergy.solarcharger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Load/I': dummy, - '/FirmwareVersion': dummy}, - 'com.victronenergy.battery': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/DeviceInstance': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy, - '/Sense/Current': dummy, - '/TimeToGo': dummy, - '/ConsumedAmphours': dummy, - '/ProductId': dummy, - '/CustomName': dummy, - '/Info/MaxChargeVoltage': dummy}, - 'com.victronenergy.vebus' : { - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/ActiveIn/L1/P': dummy, - '/Ac/ActiveIn/L2/P': dummy, - '/Ac/ActiveIn/L3/P': dummy, - '/Ac/ActiveIn/L1/I': dummy, - '/Ac/ActiveIn/L2/I': dummy, - '/Ac/ActiveIn/L3/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/I': dummy, - '/Connected': dummy, - '/ProductId': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Mode': dummy, - '/State': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.fuelcell': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy}, - 'com.victronenergy.charger': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/1/Voltage': dummy, - '/Dc/1/Current': dummy, - '/Dc/2/Voltage': dummy, - '/Dc/2/Current': dummy}, - 'com.victronenergy.grid' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy}, - 'com.victronenergy.genset' : { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/ProductId' : dummy, - '/DeviceType' : dummy, - '/Ac/L1/Power': dummy, - '/Ac/L2/Power': dummy, - '/Ac/L3/Power': dummy, - '/Ac/L1/Current': dummy, - '/Ac/L2/Current': dummy, - '/Ac/L3/Current': dummy, - '/StarterVoltage': dummy}, - 'com.victronenergy.settings' : { - '/Settings/SystemSetup/AcInput1' : dummy, - '/Settings/SystemSetup/AcInput2' : dummy, - '/Settings/CGwacs/RunWithoutGridMeter' : dummy, - '/Settings/System/TimeZone' : dummy}, - 'com.victronenergy.temperature': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy}, - 'com.victronenergy.inverter': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/S': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/S': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/S': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.multi': { - '/Connected': dummy, - '/ProductName': dummy, - '/Mgmt/Connection': dummy, - '/Dc/0/Voltage': dummy, - '/Dc/0/Current': dummy, - '/Dc/0/Power': dummy, - '/Ac/ActiveIn/ActiveInput': dummy, - '/Ac/In/1/Type': dummy, - '/Ac/In/2/Type': dummy, - '/Ac/In/1/L1/P': dummy, - '/Ac/In/1/L1/I': dummy, - '/Ac/In/2/L1/P': dummy, - '/Ac/In/2/L1/I': dummy, - '/Ac/Out/L1/P': dummy, - '/Ac/Out/L1/V': dummy, - '/Ac/Out/L1/I': dummy, - '/Ac/In/1/L2/P': dummy, - '/Ac/In/1/L2/I': dummy, - '/Ac/In/2/L2/P': dummy, - '/Ac/In/2/L2/I': dummy, - '/Ac/Out/L2/P': dummy, - '/Ac/Out/L2/V': dummy, - '/Ac/Out/L2/I': dummy, - '/Ac/In/1/L3/P': dummy, - '/Ac/In/1/L3/I': dummy, - '/Ac/In/2/L3/P': dummy, - '/Ac/In/2/L3/I': dummy, - '/Ac/Out/L3/P': dummy, - '/Ac/Out/L3/V': dummy, - '/Ac/Out/L3/I': dummy, - '/Yield/Power': dummy, - '/Soc': dummy}, - 'com.victronenergy.dcsystem': { - '/Dc/0/Voltage': dummy, - '/Dc/0/Power': dummy - }, - 'com.victronenergy.alternator': { - '/Dc/0/Power': dummy - } - } - - self._modules = [ - delegates.Multi(), - delegates.HubTypeSelect(), - delegates.VebusSocWriter(), - delegates.ServiceMapper(), - delegates.RelayState(), - delegates.BuzzerControl(), - delegates.LgCircuitBreakerDetect(), - delegates.BatterySoc(self), - delegates.Dvcc(self), - delegates.BatterySense(self), - delegates.BatterySettings(self), - delegates.SystemState(self), - delegates.BatteryLife(), - delegates.ScheduledCharging(), - delegates.SourceTimers(), - delegates.BatteryData(), - delegates.Gps(), - delegates.AcInputs(), - delegates.GensetStartStop(), - delegates.SocSync(self), - delegates.PvInverters(), - delegates.BatteryService(self), - delegates.CanBatterySense(), - delegates.InverterCharger(), - delegates.DynamicEss(), - delegates.LoadShedding()] - - for m in self._modules: - for service, paths in m.get_input(): - s = dbus_tree.setdefault(service, {}) - for path in paths: - s[path] = dummy - - self._dbusmonitor = self._create_dbus_monitor(dbus_tree, valueChangedCallback=self._dbus_value_changed, - deviceAddedCallback=self._device_added, deviceRemovedCallback=self._device_removed) - - # Used to store AC output maximum values for the scenarios: - # No AC input connected - # Ac input 1 connected - # Ac input 2 connected - self._acMaxima = { - 'NoAcIn': 0, - 'AcIn1': 0, - 'AcIn2': 0 - } - - self._minMaxPaths = { - '/Ac/In/0/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/1/Current/Min': [float(0), -float("inf"), 0], - '/Ac/In/0/Current/Max': [float(0), 0, float("inf")], - '/Ac/In/1/Current/Max': [float(0), 0, float("inf")], - '/Dc/Input/Power/Max': [float(0), 0, float("inf")], - '/Dc/System/Power/Max': [float(0), 0, float("inf")], - '/Pv/Power/Max': [float(0), 0, float("inf")] - } - - for p in self._acMaxima.keys(): - self._minMaxPaths['/Ac/%s/Consumption/Current/Max' % p] = [float(0), 0, float("inf")] - - # Connect to localsettings - supported_settings = { - 'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0], - 'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1], - 'useacout': ['/Settings/SystemSetup/HasAcOutSystem', 1, 0, 1], - 'gaugeautomax': ['/Settings/Gui/Gauges/AutoMax', 1, 0, 1]} - - for p, s in self._minMaxPaths.items(): - supported_settings[p] = ['/Settings/Gui/Gauges' + p, s[0], s[1], s[2]] - - for m in self._modules: - for setting in m.get_settings(): - supported_settings[setting[0]] = list(setting[1:]) - - self._settings = self._create_settings(supported_settings, self._handlechangedsetting) - - self._dbusservice = self._create_dbus_service() - - for m in self._modules: - m.set_sources(self._dbusmonitor, self._settings, self._dbusservice) - - # At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely - # identifying the CCGX. - self._dbusservice.add_path('/Serial', value=get_vrm_portal_id()) - self._dbusservice.add_path( - '/AvailableBatteryServices', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AvailableBatteryMeasurements', value=None) - self._dbusservice.add_path( - '/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/ActiveBatteryService', value=None, gettextcallback=self._gettext) - self._dbusservice.add_path( - '/Dc/Battery/BatteryService', value=None) - self._summeditems = { - '/Ac/Grid/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Grid/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Grid/ProductId': {'gettext': '%s'}, - '/Ac/Grid/DeviceType': {'gettext': '%s'}, - '/Ac/Genset/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Genset/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Genset/ProductId': {'gettext': '%s'}, - '/Ac/Genset/DeviceType': {'gettext': '%s'}, - '/Ac/ConsumptionOnOutput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnOutput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnOutput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ConsumptionOnInput/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ConsumptionOnInput/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L2/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L3/Power': {'gettext': '%.0F W'}, - '/Ac/Consumption/L1/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L2/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/L3/Current': {'gettext': '%.1F A'}, - '/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'}, - '/Dc/Pv/Power': {'gettext': '%.0F W'}, - '/Dc/Pv/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Voltage': {'gettext': '%.2F V'}, - '/Dc/Battery/VoltageService': {'gettext': '%s'}, - '/Dc/Battery/Current': {'gettext': '%.1F A'}, - '/Dc/Battery/Power': {'gettext': '%.0F W'}, - '/Dc/Battery/State': {'gettext': lambda v: ({ - self.STATE_IDLE: 'Idle', - self.STATE_CHARGING: 'Charging', - self.STATE_DISCHARGING: 'Discharging'}.get(v, 'Unknown'))}, - '/Dc/Battery/TimeToGo': {'gettext': '%.0F s'}, - '/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'}, - '/Dc/Battery/ProductId': {'gettext': '0x%x'}, - '/Dc/Charger/Power': {'gettext': '%.0F %%'}, - '/Dc/FuelCell/Power': {'gettext': '%.0F %%'}, - '/Dc/Alternator/Power': {'gettext': '%.0F W'}, - '/Dc/System/Power': {'gettext': '%.0F W'}, - '/Dc/System/MeasurementType': {'gettext': '%d'}, - '/Ac/ActiveIn/Source': {'gettext': '%s'}, - '/Ac/ActiveIn/L1/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L2/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L3/Power': {'gettext': '%.0F W'}, - '/Ac/ActiveIn/L1/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L2/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/L3/Current': {'gettext': '%.1F A'}, - '/Ac/ActiveIn/NumberOfPhases': {'gettext': '%d'}, - '/Ac/In/0/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/0/Current/Max': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Min': {'gettext': '%.1F'}, - '/Ac/In/1/Current/Max': {'gettext': '%.1F'}, - '/Ac/Consumption/Current/Max': {'gettext': '%.1F'}, - '/Pv/Power/Max': {'gettext': '%d'}, - '/Dc/Input/Power/Max': {'gettext': '%d'}, - '/Dc/System/Power/Max': {'gettext': '%d'} - } - - for m in self._modules: - self._summeditems.update(m.get_output()) - - for path in self._summeditems.keys(): - self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext) - - self._batteryservice = None - self._determinebatteryservice() - - if self._batteryservice is None: - logger.info("Battery service initialized to None (setting == %s)" % - self._settings['batteryservice']) - - self._changed = True - for service, instance in self._dbusmonitor.get_service_list().items(): - self._device_added(service, instance, do_service_change=False) - - self._handleservicechange() - self._updatevalues() - - GLib.timeout_add(1000, exit_on_error, self._handletimertick) - - def _create_dbus_monitor(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_settings(self, *args, **kwargs): - raise Exception("This function should be overridden") - - def _create_dbus_service(self): - raise Exception("This function should be overridden") - - def _handlechangedsetting(self, setting, oldvalue, newvalue): - self._determinebatteryservice() - self._changed = True - - # Give our delegates a chance to react on a settings change - for m in self._modules: - m.settings_changed(setting, oldvalue, newvalue) - - def _find_device_instance(self, serviceclass, instance): - """ Gets a mapping of services vs DeviceInstance using - get_service_list. Then searches for the specified DeviceInstance - and returns the service name. """ - services = self._dbusmonitor.get_service_list(classfilter=serviceclass) - - for k, v in services.items(): - if v == instance: - return k - return None - - def _determinebatteryservice(self): - auto_battery_service = self._autoselect_battery_service() - auto_battery_measurement = None - auto_selected = False - if auto_battery_service is not None: - services = self._dbusmonitor.get_service_list() - if auto_battery_service in services: - auto_battery_measurement = \ - self._get_instance_service_name(auto_battery_service, services[auto_battery_service]) - auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0' - self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement - - if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT: - auto_selected = True - newbatteryservice = auto_battery_service - self._dbusservice['/AutoSelectedBatteryService'] = ( - 'No battery monitor found' if newbatteryservice is None else - self._get_readable_service_name(newbatteryservice)) - - elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY: - self._dbusservice['/AutoSelectedBatteryService'] = None - newbatteryservice = None - - else: - self._dbusservice['/AutoSelectedBatteryService'] = None - - s = self._settings['batteryservice'].split('/') - if len(s) != 2: - logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice']) - serviceclass = s[0] - instance = int(s[1]) if len(s) == 2 else None - - # newbatteryservice might turn into None if a chosen battery - # monitor no longer exists. Don't auto change the setting (it might - # come back) and don't autoselect another. - newbatteryservice = self._find_device_instance(serviceclass, instance) - - if newbatteryservice != self._batteryservice: - services = self._dbusmonitor.get_service_list() - instance = services.get(newbatteryservice, None) - if instance is None: - battery_service = None - else: - battery_service = self._get_instance_service_name(newbatteryservice, instance) - self._dbusservice['/ActiveBatteryService'] = battery_service - logger.info("Battery service, setting == %s, changed from %s to %s (%s)" % - (self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance)) - - # Battery service has changed. Notify delegates. - self._dbusservice['/Dc/Battery/BatteryService'] = self._batteryservice = newbatteryservice - for m in self._modules: - m.battery_service_changed(auto_selected, self._batteryservice, newbatteryservice) - - def _autoselect_battery_service(self): - # Default setting business logic: - # first try to use a battery service (BMV or Lynx Shunt VE.Can). If there - # is more than one battery service, just use a random one. If no battery service is - # available, check if there are not Solar chargers and no normal chargers. If they are not - # there, assume this is a hub-2, hub-3 or hub-4 system and use VE.Bus SOC. - batteries = self._get_connected_service_list('com.victronenergy.battery') - - # Pick the battery service that has the lowest DeviceInstance, giving - # preference to those with a BMS. - if len(batteries) > 0: - batteries = [ - (not self._dbusmonitor.seen(s, '/Info/MaxChargeVoltage'), i, s) - for s, i in batteries.items()] - return sorted(batteries, key=lambda x: x[:2])[0][2] - - # No battery services, and there is a charger in the system. Abandon - # hope. - if self._get_first_connected_service('com.victronenergy.charger') is not None: - return None - - # Also no Multi, then give up. - vebus_service = self._get_service_having_lowest_instance('com.victronenergy.vebus') - if vebus_service is None: - # No VE.Bus, but maybe there is an inverter with built-in SOC - # tracking, eg RS Smart or Multi RS. - inverter = self._get_service_having_lowest_instance('com.victronenergy.multi') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - inverter = self._get_service_having_lowest_instance('com.victronenergy.inverter') - if inverter and self._dbusmonitor.get_value(inverter[0], '/Soc') is not None: - return inverter[0] - - return None - - # There is a Multi, it supports tracking external charge current from - # solarchargers, and there are no DC loads. Then use it. - if self._dbusmonitor.get_value( - vebus_service[0], '/ExtraBatteryCurrent') is not None \ - and self._get_first_connected_service('com.victronenergy.dcsystem') is None \ - and self._settings['hasdcsystem'] == 0: - return vebus_service[0] - - # Multi does not support tracking solarcharger current, and we have - # solar chargers. Then we cannot use it. - if self._get_first_connected_service('com.victronenergy.solarcharger') is not None: - return None - - # Only a Multi, no other chargers. Then we can use it. - return vebus_service[0] - - @property - def batteryservice(self): - return self._batteryservice - - # Called on a one second timer - def _handletimertick(self): - if self._changed: - self._updatevalues() - self._changed = False - - return True # keep timer running - - def _updatevalues(self): - # ==== PREPARATIONS ==== - newvalues = {} - - # Set the user timezone - if 'TZ' not in os.environ: - tz = self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/System/TimeZone') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - # Determine values used in logic below - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - vebuspower = 0 - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current') - if v is not None and i is not None: - vebuspower += v * i - - # ==== PVINVERTERS ==== - # Work is done in pv-inverter delegate. Ideally all of this should - # happen in update_values in the delegate, but these values are - # used below in calculating consumption, so until this is less - # unwieldy this has to stay here. - # TODO this can go away once consumption below no longer relies - # on these values, or has moved to its own delegate. - newvalues.update(delegates.PvInverters.instance.get_totals()) - self._compute_number_of_phases('/Ac/PvOnGrid', newvalues) - self._compute_number_of_phases('/Ac/PvOnOutput', newvalues) - self._compute_number_of_phases('/Ac/PvOnGenset', newvalues) - - # ==== SOLARCHARGERS ==== - solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger') - solarcharger_batteryvoltage = None - solarcharger_batteryvoltage_service = None - solarchargers_charge_power = 0 - solarchargers_loadoutput_power = None - - for solarcharger in solarchargers: - v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage') - if v is None: - continue - i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current') - if i is None: - continue - l = self._dbusmonitor.get_value(solarcharger, '/Load/I', 0) - - if l is not None: - if solarchargers_loadoutput_power is None: - solarchargers_loadoutput_power = l * v - else: - solarchargers_loadoutput_power += l * v - - solarchargers_charge_power += v * i - - # Note that this path is not in the _summeditems{}, making for it to not be - # published on D-Bus. Which fine. The only one needing it is the vebussocwriter- - # delegate. - if '/Dc/Pv/ChargeCurrent' not in newvalues: - newvalues['/Dc/Pv/ChargeCurrent'] = i - else: - newvalues['/Dc/Pv/ChargeCurrent'] += i - - if '/Dc/Pv/Power' not in newvalues: - newvalues['/Dc/Pv/Power'] = v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] = _safeadd(i, l) - solarcharger_batteryvoltage = v - solarcharger_batteryvoltage_service = solarcharger - else: - newvalues['/Dc/Pv/Power'] += v * _safeadd(i, l) - newvalues['/Dc/Pv/Current'] += _safeadd(i, l) - - # ==== FUELCELLS ==== - fuelcells = self._dbusmonitor.get_service_list('com.victronenergy.fuelcell') - fuelcell_batteryvoltage = None - fuelcell_batteryvoltage_service = None - for fuelcell in fuelcells: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Voltage') - if v is None: - continue - - fuelcell_batteryvoltage = v - fuelcell_batteryvoltage_service = fuelcell - - i = self._dbusmonitor.get_value(fuelcell, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/FuelCell/Power' not in newvalues: - newvalues['/Dc/FuelCell/Power'] = v * i - else: - newvalues['/Dc/FuelCell/Power'] += v * i - - # ==== ALTERNATOR ==== - alternators = self._dbusmonitor.get_service_list('com.victronenergy.alternator') - for alternator in alternators: - # Assume the battery connected to output 0 is the main battery - p = self._dbusmonitor.get_value(alternator, '/Dc/0/Power') - if p is None: - continue - - if '/Dc/Alternator/Power' not in newvalues: - newvalues['/Dc/Alternator/Power'] = p - else: - newvalues['/Dc/Alternator/Power'] += p - - # ==== CHARGERS ==== - chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger') - charger_batteryvoltage = None - charger_batteryvoltage_service = None - for charger in chargers: - # Assume the battery connected to output 0 is the main battery - v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage') - if v is None: - continue - - charger_batteryvoltage = v - charger_batteryvoltage_service = charger - - i = self._dbusmonitor.get_value(charger, '/Dc/0/Current') - if i is None: - continue - - if '/Dc/Charger/Power' not in newvalues: - newvalues['/Dc/Charger/Power'] = v * i - else: - newvalues['/Dc/Charger/Power'] += v * i - - # ==== Other Inverters and Inverter/Chargers ==== - _other_inverters = sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.multi').items()) + \ - sorted((di, s) for s, di in self._dbusmonitor.get_service_list('com.victronenergy.inverter').items()) - non_vebus_inverters = [x[1] for x in _other_inverters] - non_vebus_inverter = None - if non_vebus_inverters: - non_vebus_inverter = non_vebus_inverters[0] - - # For RS Smart and Multi RS, add PV to the yield - for i in non_vebus_inverters: - if (pv_yield := self._dbusmonitor.get_value(i, "/Yield/Power")) is not None: - newvalues['/Dc/Pv/Power'] = newvalues.get('/Dc/Pv/Power', 0) + pv_yield - - # Used lower down, possibly needed for battery values as well - dcsystems = self._dbusmonitor.get_service_list('com.victronenergy.dcsystem') - - # ==== BATTERY ==== - if self._batteryservice is not None: - batteryservicetype = self._batteryservice.split('.')[2] - assert batteryservicetype in ('battery', 'vebus', 'inverter', 'multi') - - newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo') - newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours') - newvalues['/Dc/Battery/ProductId'] = self._dbusmonitor.get_value(self._batteryservice, '/ProductId') - - if batteryservicetype in ('battery', 'inverter', 'multi'): - newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power') - - elif batteryservicetype == 'vebus': - vebus_voltage = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage') - vebus_current = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current') - vebus_power = None if vebus_voltage is None or vebus_current is None else vebus_current * vebus_voltage - newvalues['/Dc/Battery/Voltage'] = vebus_voltage - newvalues['/Dc/Battery/VoltageService'] = self._batteryservice - if self._settings['hasdcsystem'] == 1 or dcsystems: - # hasdcsystem will normally disqualify the multi from being - # auto-selected as battery monitor, so the only way we're - # here is if the user explicitly selected the multi as the - # battery service - newvalues['/Dc/Battery/Current'] = vebus_current - if vebus_power is not None: - newvalues['/Dc/Battery/Power'] = vebus_power - else: - battery_power = _safeadd(solarchargers_charge_power, vebus_power) - newvalues['/Dc/Battery/Current'] = battery_power / vebus_voltage if vebus_voltage is not None and vebus_voltage > 0 else None - newvalues['/Dc/Battery/Power'] = battery_power - - - p = newvalues.get('/Dc/Battery/Power', None) - if p is not None: - if p > 30: - newvalues['/Dc/Battery/State'] = self.STATE_CHARGING - elif p < -30: - newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING - else: - newvalues['/Dc/Battery/State'] = self.STATE_IDLE - - else: - # The battery service is not a BMS/BMV or a suitable vebus. A - # suitable vebus is defined as one explicitly selected by the user, - # or one that was automatically selected for SOC tracking. We may - # however still have a VE.Bus, just not one that can accurately - # track SOC. If we have one, use it as voltage source. Otherwise - # try a solar charger, a charger, a vedirect inverter or a dcsource - # as fallbacks. - batteryservicetype = None - vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus') - for vebus in vebusses: - v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage') - s = self._dbusmonitor.get_value(vebus, '/State') - if v is not None and s not in (0, None): - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = vebus - break # Skip the else below - else: - # No suitable vebus voltage, try other devices - if non_vebus_inverter is not None and (v := self._dbusmonitor.get_value(non_vebus_inverter, '/Dc/0/Voltage')) is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = non_vebus_inverter - elif solarcharger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = solarcharger_batteryvoltage_service - elif charger_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = charger_batteryvoltage_service - elif fuelcell_batteryvoltage is not None: - newvalues['/Dc/Battery/Voltage'] = fuelcell_batteryvoltage - newvalues['/Dc/Battery/VoltageService'] = fuelcell_batteryvoltage_service - elif dcsystems: - # Get voltage from first dcsystem - s = next(iter(dcsystems.keys())) - v = self._dbusmonitor.get_value(s, '/Dc/0/Voltage') - if v is not None: - newvalues['/Dc/Battery/Voltage'] = v - newvalues['/Dc/Battery/VoltageService'] = s - - # We have no suitable battery monitor, so power and current data - # is not available. We can however calculate it from other values, - # if we have at least a battery voltage. - if '/Dc/Battery/Voltage' in newvalues: - dcsystempower = _safeadd(0, *(self._dbusmonitor.get_value(s, - '/Dc/0/Power', 0) for s in dcsystems)) - if dcsystems or self._settings['hasdcsystem'] == 0: - # Either DC loads are monitored, or there are no - # unmonitored DC loads or chargers: derive battery watts - # and amps from vebus, solarchargers, chargers and measured - # loads. - p = solarchargers_charge_power + newvalues.get('/Dc/Charger/Power', 0) + vebuspower - dcsystempower - voltage = newvalues['/Dc/Battery/Voltage'] - newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None - newvalues['/Dc/Battery/Power'] = p - - # ==== SYSTEM POWER ==== - # Look for dcsytem devices, add them together. Otherwise, if enabled, - # calculate it - if dcsystems: - newvalues['/Dc/System/MeasurementType'] = 1 # measured - newvalues['/Dc/System/Power'] = 0 - for meter in dcsystems: - newvalues['/Dc/System/Power'] = _safeadd(newvalues['/Dc/System/Power'], - self._dbusmonitor.get_value(meter, '/Dc/0/Power')) - elif self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery': - # Calculate power being generated/consumed by not measured devices in the network. - # For MPPTs, take all the power, including power going out of the load output. - # /Dc/System: positive: consuming power - # VE.Bus: Positive: current flowing from the Multi to the dc system or battery - # Solarcharger & other chargers: positive: charging - # battery: Positive: charging battery. - # battery = solarcharger + charger + ve.bus - system - - battery_power = newvalues.get('/Dc/Battery/Power') - if battery_power is not None: - dc_pv_power = newvalues.get('/Dc/Pv/Power', 0) - charger_power = newvalues.get('/Dc/Charger/Power', 0) - fuelcell_power = newvalues.get('/Dc/FuelCell/Power', 0) - alternator_power = newvalues.get('/Dc/Alternator/Power', 0) - - # If there are VE.Direct inverters, remove their power from the - # DC estimate. This is done using the AC value when the DC - # power values are not available. - inverter_power = 0 - for i in non_vebus_inverters: - inverter_current = self._dbusmonitor.get_value(i, '/Dc/0/Current') - if inverter_current is not None: - inverter_power += self._dbusmonitor.get_value( - i, '/Dc/0/Voltage', 0) * inverter_current - else: - inverter_power -= self._dbusmonitor.get_value( - i, '/Ac/Out/L1/V', 0) * self._dbusmonitor.get_value( - i, '/Ac/Out/L1/I', 0) - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - # FIXME In future we will subtract alternator power from the - # calculated DC power, because it will be individually - # displayed. For now, we leave it out so that in the current - # version of Venus it does not break user's expectations. - #newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - alternator_power - newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + fuelcell_power + vebuspower + inverter_power - battery_power - - elif self._settings['hasdcsystem'] == 1 and solarchargers_loadoutput_power is not None: - newvalues['/Dc/System/MeasurementType'] = 0 # estimated - newvalues['/Dc/System/Power'] = solarchargers_loadoutput_power - - # ===== AC IN SOURCE ===== - multi_path = getattr(delegates.Multi.instance.multi, 'service', None) - ac_in_source = None - active_input = None - if multi_path is None: - # Check if we have an non-VE.Bus inverter. - if non_vebus_inverter is not None: - if (active_input := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/ActiveIn/ActiveInput')) is not None and \ - active_input in (0, 1) and \ - (active_type := self._dbusmonitor.get_value(non_vebus_inverter, '/Ac/In/{}/Type'.format(active_input + 1))) is not None: - ac_in_source = active_type - else: - ac_in_source = 240 - else: - active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput') - if active_input == 0xF0: - # Not connected - ac_in_source = 240 - elif active_input is not None: - settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1) - ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path) - newvalues['/Ac/ActiveIn/Source'] = ac_in_source - - # ===== GRID METERS & CONSUMPTION ==== - grid_meter = delegates.AcInputs.instance.gridmeter - genset_meter = delegates.AcInputs.instance.gensetmeter - - # Make an educated guess as to what is being consumed from an AC source. If ac_in_source - # indicates grid, genset or shore, we use that. If the Multi is off, or disconnected through - # a relay assistant or otherwise, then assume the presence of a .grid or .genset service indicates - # presence of that AC source. If both are available, then give up. This decision making is here - # so the GUI has something to present even if the Multi is off. - ac_in_guess = ac_in_source - if ac_in_guess in (None, 0xF0): - if genset_meter is None and grid_meter is not None: - ac_in_guess = 1 - elif grid_meter is None and genset_meter is not None: - ac_in_guess = 2 - - consumption = { "L1" : None, "L2" : None, "L3" : None } - currentconsumption = { "L1" : None, "L2" : None, "L3" : None } - for device_type, em, _types in (('Grid', grid_meter, (1, 3)), ('Genset', genset_meter, (2,))): - # If a grid meter is present we use values from it. If not, we look at the multi. If it has - # AcIn1 or AcIn2 connected to the grid, we use those values. - # com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter. - # com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1 - # or AcIn2 as possible grid connection. - uses_active_input = ac_in_source in _types - for phase in consumption: - p = None - mc = None - pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase)) - pvcurrent = newvalues.get('/Ac/PvOn%s/%s/Current' % (device_type, phase)) - if em is not None: - p = self._dbusmonitor.get_value(em.service, '/Ac/%s/Power' % phase) - mc = self._dbusmonitor.get_value(em.service, '/Ac/%s/Current' % phase) - # Compute consumption between energy meter and multi (meter power - multi AC in) and - # add an optional PV inverter on input to the mix. - c = None - cc = None - if uses_active_input: - if multi_path is not None: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) - cc = _safeadd(cc, -self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase)) - except TypeError: - pass - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - try: - c = _safeadd(c, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input+1, phase))) - cc = _safeadd(cc, -self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input+1, phase))) - except TypeError: - pass - - # If there's any power coming from a PV inverter in the inactive AC in (which is unlikely), - # it will still be used, because there may also be a load in the same ACIn consuming - # power, or the power could be fed back to the net. - c = _safeadd(c, p, pvpower) - cc = _safeadd(cc, mc, pvcurrent) - consumption[phase] = _safeadd(consumption[phase], _safemax(0, c)) - currentconsumption[phase] = _safeadd(currentconsumption[phase], _safemax(0, cc)) - else: - if uses_active_input: - if multi_path is not None and ( - p := self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)) is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - mc = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/I' % phase) - elif non_vebus_inverter is not None and active_input in (0, 1): - for i in non_vebus_inverters: - p = _safeadd(p, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/P' % (active_input + 1, phase))) - mc = _safeadd(mc, - self._dbusmonitor.get_value(i, '/Ac/In/%d/%s/I' % (active_input + 1, phase))) - if p is not None: - consumption[phase] = _safeadd(0, consumption[phase]) - currentconsumption[phase] = _safeadd(0, currentconsumption[phase]) - - # No relevant energy meter present. Assume there is no load between the grid and the multi. - # There may be a PV inverter present though (Hub-3 setup). - try: - p = _safeadd(p, -pvpower) - mc = _safeadd(mc, -pvcurrent) - except TypeError: - pass - - newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p - newvalues['/Ac/%s/%s/Current' % (device_type, phase)] = mc - if ac_in_guess in _types: - newvalues['/Ac/ActiveIn/%s/Power' % (phase,)] = p - newvalues['/Ac/ActiveIn/%s/Current' % (phase,)] = mc - - self._compute_number_of_phases('/Ac/%s' % device_type, newvalues) - self._compute_number_of_phases('/Ac/ActiveIn', newvalues) - - product_id = None - device_type_id = None - if em is not None: - product_id = em.product_id - device_type_id = em.device_type - if product_id is None and uses_active_input: - if multi_path is not None: - product_id = self._dbusmonitor.get_value(multi_path, '/ProductId') - elif non_vebus_inverter is not None: - product_id = self._dbusmonitor.get_value(non_vebus_inverter, '/ProductId') - newvalues['/Ac/%s/ProductId' % device_type] = product_id - newvalues['/Ac/%s/DeviceType' % device_type] = device_type_id - - # If we have an ESS system and RunWithoutGridMeter is set, there cannot be load on the AC-In, so it - # must be on AC-Out. Hence we do calculate AC-Out consumption even if 'useacout' is disabled. - # Similarly all load are by definition on the output if this is not an ESS system. - use_ac_out = \ - self._settings['useacout'] == 1 or \ - (multi_path is not None and self._dbusmonitor.get_value(multi_path, '/Hub4/AssistantId') not in (4, 5)) or \ - self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/CGwacs/RunWithoutGridMeter') == 1 - for phase in consumption: - c = None - a = None - if use_ac_out: - c = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase) - a = newvalues.get('/Ac/PvOnOutput/%s/Current' % phase) - if multi_path is None: - for inv in non_vebus_inverters: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/P' % phase) - i = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/I' % phase) - - # Some models don't show power, try apparent power, - # else calculate it - if ac_out is None: - ac_out = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/S' % phase) - if ac_out is None: - u = self._dbusmonitor.get_value(inv, '/Ac/Out/%s/V' % phase) - if None not in (i, u): - ac_out = i * u - c = _safeadd(c, ac_out) - a = _safeadd(a, i) - else: - ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase) - c = _safeadd(c, ac_out) - i_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/I' % phase) - a = _safeadd(a, i_out) - c = _safemax(0, c) - a = _safemax(0, a) - newvalues['/Ac/ConsumptionOnOutput/%s/Power' % phase] = c - newvalues['/Ac/ConsumptionOnOutput/%s/Current' % phase] = a - newvalues['/Ac/ConsumptionOnInput/%s/Power' % phase] = consumption[phase] - newvalues['/Ac/ConsumptionOnInput/%s/Current' % phase] = currentconsumption[phase] - newvalues['/Ac/Consumption/%s/Power' % phase] = _safeadd(consumption[phase], c) - newvalues['/Ac/Consumption/%s/Current' % phase] = _safeadd(currentconsumption[phase], a) - self._compute_number_of_phases('/Ac/Consumption', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnOutput', newvalues) - self._compute_number_of_phases('/Ac/ConsumptionOnInput', newvalues) - - for m in self._modules: - m.update_values(newvalues) - - # ==== UPDATE MINIMUM AND MAXIMUM LEVELS ==== - # min/max values are stored in localsettings and synched once in a while. - # values are stored under /Settings/Gui/Briefview - # /Settings/Gui/Gauges/AutoMax: - # 1-> Automatic: Maxima are updated automatically and synched to localsettings - # 0-> Manual: Maxima are pulled from localsettings. - # min/max computations are done here because the _updatevalues method abstracts them - # away from the delegates. - - # AC output - # This maximum is maintained for 3 situations: - # 1: AC input 1 is connected - # 2: AC input 2 is connected - # 3: No AC input is connected - # All 3 scenarios may lead to different maximum values since the capabilities of the system changes. - # So 3 different maxima are stored and relayed to /Ac/Consumption/Current/Max based on the active scenario. - activeIn = 'AcIn1' if (self._dbusservice['/Ac/In/0/Connected'] == 1) else \ - 'AcIn2' if (self._dbusservice['/Ac/In/1/Connected'] == 1) else \ - 'NoAcIn' - - # Quattro has 2 AC inputs which cannot be active simultaneously. - # activeIn needs to 1 when 'Ac/In/1/Connected' is 1 and can be 0 otherwise. - if (self._settings['gaugeautomax']): - activeInNr = int(activeIn[-1]) -1 if activeIn != 'NoAcIn' else None - - # AC input - # Minimum values occur when feeding back to the grid. - # For the minimum value, make sure it is 0 at its maximum. - # Update correct '/Ac/In/..' based on the current active input. - # When no inputs are active, paths '/Ac/In/[0/1]/Current/[Min/Max] will all be invalidated. - if(activeInNr != None): - newvalues['/Ac/In/%s/Current/Min' % activeInNr] = min(0, - self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or float("inf"), - newvalues.get('/Ac/ActiveIn/L1/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L2/Current') or float("inf"), - newvalues.get('/Ac/ActiveIn/L3/Current') or float("inf")) - - newvalues['/Ac/In/%s/Current/Max' % activeInNr] = max(self._dbusservice['/Ac/In/%s/Current/Min' % activeInNr] or 0, - newvalues.get('/Ac/ActiveIn/L1/Current') or 0, - newvalues.get('/Ac/ActiveIn/L2/Current') or 0, - newvalues.get('/Ac/ActiveIn/L3/Current') or 0) - - self._acMaxima[activeIn] = max(self._acMaxima[activeIn], - newvalues.get('/Ac/Consumption/L1/Current') or 0, - newvalues.get('/Ac/Consumption/L2/Current') or 0, - newvalues.get('/Ac/Consumption/L3/Current') or 0) - - newvalues['/Ac/Consumption/Current/Max'] = self._acMaxima[activeIn] - - # DC input - newvalues['/Dc/Input/Power/Max'] = max(self._dbusservice['/Dc/Input/Power/Max'] or 0, - sum([newvalues.get('/Dc/Charger/Power') or 0, - newvalues.get('/Dc/FuelCell/Power') or 0, - newvalues.get('/Dc/Alternator/Power') or 0])) - - # DC output - newvalues['/Dc/System/Power/Max'] = _safemax(self._dbusservice['/Dc/System/Power/Max'] or 0, - newvalues.get('/Dc/System/Power') or 0) - - # PV power - newvalues['/Pv/Power/Max'] = _safemax(self._dbusservice['/Pv/Power/Max'] or 0, - _safeadd(newvalues.get('/Dc/Pv/Power') or 0, - self._dbusservice['/Ac/PvOnGrid/L1/Power'], - self._dbusservice['/Ac/PvOnGrid/L2/Power'], - self._dbusservice['/Ac/PvOnGrid/L3/Power'], - self._dbusservice['/Ac/PvOnGenset/L1/Power'], - self._dbusservice['/Ac/PvOnGenset/L2/Power'], - self._dbusservice['/Ac/PvOnGenset/L3/Power'], - self._dbusservice['/Ac/PvOnOutput/L1/Power'], - self._dbusservice['/Ac/PvOnOutput/L2/Power'], - self._dbusservice['/Ac/PvOnOutput/L3/Power'])) - - # Sync max values to localsettings (once each second) - for p in self._minMaxPaths.keys(): - if (p in newvalues and newvalues[p] != self._settings[p]): - self._settings[p] = newvalues[p] - - # Store the ac maxima values for the 3 different scenarios. These aren't in newvalues. - if(self._acMaxima[activeIn] != self._settings['/Ac/%s/Consumption/Current/Max' % activeIn]): - self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] = self._acMaxima[activeIn] - - # Manual mode: relay min/max settings from localsettings to newvalues - # We have to fill newvalues on every iteration here because if we don't the value in dbusservice is invalidated - else: - for p in self._minMaxPaths.keys(): - newvalues[p] = self._settings[p] - - newvalues['/Ac/Consumption/Current/Max'] = self._settings['/Ac/%s/Consumption/Current/Max' % activeIn] - - # ==== UPDATE DBUS ITEMS ==== - with self._dbusservice as sss: - for path in self._summeditems.keys(): - # Why the None? Because we want to invalidate things we don't have anymore. - sss[path] = newvalues.get(path, None) - - def _handleservicechange(self): - # Update the available battery monitor services, used to populate the dropdown in the settings. - # Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example - # "battery/245". The value is the name to show to the user in the dropdown. The full dbus- - # servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not - # fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those. - - services = self._get_connected_service_list('com.victronenergy.vebus') - services.update(self._get_connected_service_list('com.victronenergy.battery')) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.multi').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - services.update({k: v for k, v in self._get_connected_service_list( - 'com.victronenergy.inverter').items() if self._dbusmonitor.get_value(k, '/Soc') is not None}) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance) - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul) - - ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'} - # For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as - # well. - for servicename, instance in services.items(): - key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0' - ul[key] = self._get_readable_service_name(servicename) - self._dbusservice['/AvailableBatteryMeasurements'] = ul - - self._determinebatteryservice() - - self._changed = True - - def _get_readable_service_name(self, servicename): - return '%s on %s' % ( - self._dbusmonitor.get_value(servicename, '/ProductName'), - self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) - - def _get_instance_service_name(self, service, instance): - return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) - - def _remove_unconnected_services(self, services): - # Workaround: because com.victronenergy.vebus is available even when there is no vebus product - # connected, remove any service that is not connected. Previously we used - # /State since mandatory path /Connected is not implemented in mk2dbus, - # but this has since been resolved. - for servicename in list(services.keys()): - if (self._dbusmonitor.get_value(servicename, '/Connected') != 1 - or self._dbusmonitor.get_value(servicename, '/ProductName') is None - or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None): - del services[servicename] - - def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance): - self._changed = True - - # Workaround because com.victronenergy.vebus is available even when there is no vebus product - # connected. - if (dbusPath in ['/Connected', '/ProductName', '/Mgmt/Connection'] or - (dbusPath == '/State' and dbusServiceName.split('.')[0:3] == ['com', 'victronenergy', 'vebus'])): - self._handleservicechange() - - # Track the timezone changes - if dbusPath == '/Settings/System/TimeZone': - tz = changes.get('Value') - if tz is not None: - os.environ['TZ'] = tz - time.tzset() - - def _device_added(self, service, instance, do_service_change=True): - if do_service_change: - self._handleservicechange() - - for m in self._modules: - m.device_added(service, instance, do_service_change) - - def _device_removed(self, service, instance): - self._handleservicechange() - - for m in self._modules: - m.device_removed(service, instance) - - def _gettext(self, path, value): - item = self._summeditems.get(path) - if item is not None: - try: - gettext = item['gettext'] - except KeyError: - pass - else: - if callable(gettext): - return gettext(value) - return gettext % value - return str(value) - - def _compute_number_of_phases(self, path, newvalues): - number_of_phases = None - for phase in range(1, 4): - p = newvalues.get('%s/L%s/Power' % (path, phase)) - if p is not None: - number_of_phases = phase - newvalues[path + '/NumberOfPhases'] = number_of_phases - - def _get_connected_service_list(self, classfilter=None): - services = self._dbusmonitor.get_service_list(classfilter=classfilter) - self._remove_unconnected_services(services) - return services - - # returns a servicename string - def _get_first_connected_service(self, classfilter): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - return next(iter(services.items()), (None,))[0] - - # returns a tuple (servicename, instance) - def _get_service_having_lowest_instance(self, classfilter=None): - services = self._get_connected_service_list(classfilter=classfilter) - if len(services) == 0: - return None - - # sort the dict by value; returns list of tuples: (value, key) - s = sorted((value, key) for (key, value) in services.items()) - return (s[0][1], s[0][0]) - - -class DbusSystemCalc(SystemCalc): - def _create_dbus_monitor(self, *args, **kwargs): - return DbusMonitor(*args, **kwargs) - - def _create_settings(self, *args, **kwargs): - bus = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() - return SettingsDevice(bus, *args, timeout=10, **kwargs) - - def _create_dbus_service(self): - venusversion, venusbuildtime = self._get_venus_versioninfo() - - dbusservice = VeDbusService('com.victronenergy.system') - dbusservice.add_mandatory_paths( - processname=__file__, - processversion=softwareVersion, - connection='data from other dbus processes', - deviceinstance=0, - productid=None, - productname=None, - firmwareversion=venusversion, - hardwareversion=None, - connected=1) - dbusservice.add_path('/FirmwareBuild', value=venusbuildtime) - return dbusservice - - def _get_venus_versioninfo(self): - try: - with open("/opt/victronenergy/version", "r") as fp: - version, software, buildtime = fp.read().split('\n')[:3] - major, minor, _, rev = re.compile('v([0-9]*)\.([0-9]*)(~([0-9]*))?').match(version).groups() - return (int(major, 16)<<16)+(int(minor, 16)<<8)+(0 if rev is None else int(rev, 16)), buildtime - except Exception: - pass - return 0, '0' - -if __name__ == "__main__": - # Argument parsing - parser = argparse.ArgumentParser( - description='Converts readings from AC-Sensors connected to a VE.Bus device in a pvinverter ' + - 'D-Bus service.' - ) - - parser.add_argument("-d", "--debug", help="set logging level to debug", - action="store_true") - - args = parser.parse_args() - - print("-------- dbus_systemcalc, v" + softwareVersion + " is starting up --------") - logger = setup_logging(args.debug) - - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus - DBusGMainLoop(set_as_default=True) - - systemcalc = DbusSystemCalc() - - # Start and run the mainloop - logger.info("Starting mainloop, responding only on events") - mainloop = GLib.MainLoop() - mainloop.run() diff --git a/changes b/changes index f4c519b8..a6974e98 100644 --- a/changes +++ b/changes @@ -1,3 +1,7 @@ +v10.53: + add support for v3.40~36 + switched several files to patched to minimize changes as firmware versions are released + v10.52: add support for v3.40~35 diff --git a/version b/version index cd77105f..874ddc53 100644 --- a/version +++ b/version @@ -1 +1 @@ -v10.52 +v10.53