diff --git a/FileSets/PatchSource/MbEditBox.qml-v3.50~3 b/FileSets/PatchSource/MbEditBox.qml-v3.50
similarity index 100%
rename from FileSets/PatchSource/MbEditBox.qml-v3.50~3
rename to FileSets/PatchSource/MbEditBox.qml-v3.50
diff --git a/FileSets/PatchSource/MbEditBox.qml-v3.50~3.orig b/FileSets/PatchSource/MbEditBox.qml-v3.50.orig
similarity index 100%
rename from FileSets/PatchSource/MbEditBox.qml-v3.50~3.orig
rename to FileSets/PatchSource/MbEditBox.qml-v3.50.orig
diff --git a/FileSets/PatchSource/MbEditBox.qml-v3.50~3.patch b/FileSets/PatchSource/MbEditBox.qml-v3.50.patch
similarity index 94%
rename from FileSets/PatchSource/MbEditBox.qml-v3.50~3.patch
rename to FileSets/PatchSource/MbEditBox.qml-v3.50.patch
index 3dd4a878..520a2765 100644
--- a/FileSets/PatchSource/MbEditBox.qml-v3.50~3.patch
+++ b/FileSets/PatchSource/MbEditBox.qml-v3.50.patch
@@ -1,5 +1,5 @@
---- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbEditBox.qml-v3.50~3.orig 2024-07-18 14:31:06
-+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbEditBox.qml-v3.50~3 2024-07-18 20:55:13
+--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbEditBox.qml-v3.50.orig 2024-07-18 14:31:06
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbEditBox.qml-v3.50 2024-07-18 20:55:13
@@ -7,6 +7,10 @@
cornerMark: !readonly && !editMode
height: expanded.y + expanded.height + 1
diff --git a/FileSets/PatchSource/MbItem.qml-v3.50~3 b/FileSets/PatchSource/MbItem.qml-v3.50
similarity index 100%
rename from FileSets/PatchSource/MbItem.qml-v3.50~3
rename to FileSets/PatchSource/MbItem.qml-v3.50
diff --git a/FileSets/PatchSource/MbItem.qml-v3.50~3.orig b/FileSets/PatchSource/MbItem.qml-v3.50.orig
similarity index 100%
rename from FileSets/PatchSource/MbItem.qml-v3.50~3.orig
rename to FileSets/PatchSource/MbItem.qml-v3.50.orig
diff --git a/FileSets/PatchSource/MbItem.qml-v3.50~3.patch b/FileSets/PatchSource/MbItem.qml-v3.50.patch
similarity index 92%
rename from FileSets/PatchSource/MbItem.qml-v3.50~3.patch
rename to FileSets/PatchSource/MbItem.qml-v3.50.patch
index f4bd05e4..31612bf3 100644
--- a/FileSets/PatchSource/MbItem.qml-v3.50~3.patch
+++ b/FileSets/PatchSource/MbItem.qml-v3.50.patch
@@ -1,5 +1,5 @@
---- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbItem.qml-v3.50~3.orig 2024-07-18 14:31:06
-+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbItem.qml-v3.50~3 2024-07-18 20:58:40
+--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbItem.qml-v3.50.orig 2024-07-18 14:31:06
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/MbItem.qml-v3.50 2024-07-18 20:58:40
@@ -14,6 +14,10 @@
height: defaultHeight
property bool show: user.accessLevel >= showAccessLevel
diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch
index 5144de45..78b076aa 100644
--- a/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch
+++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.14.patch
@@ -1,5 +1,5 @@
--- /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
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.14 2024-10-01 15:26:58
@@ -1,6 +1,8 @@
#!/usr/bin/python3 -u
# -*- coding: utf-8 -*-
diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.34.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.34.patch
index c50f5a7c..3de1c9ee 100644
--- a/FileSets/PatchSource/dbus_systemcalc.py-v3.34.patch
+++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.34.patch
@@ -1,5 +1,5 @@
--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.34.orig 2024-07-08 07:47:23
-+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.34 2024-04-04 21:38:40
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.34 2024-10-01 15:26:58
@@ -1,6 +1,8 @@
#!/usr/bin/python3 -u
# -*- coding: utf-8 -*-
diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22 b/FileSets/PatchSource/dbus_systemcalc.py-v3.50
similarity index 100%
rename from FileSets/PatchSource/dbus_systemcalc.py-v3.50~22
rename to FileSets/PatchSource/dbus_systemcalc.py-v3.50
diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.orig b/FileSets/PatchSource/dbus_systemcalc.py-v3.50.orig
similarity index 100%
rename from FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.orig
rename to FileSets/PatchSource/dbus_systemcalc.py-v3.50.orig
diff --git a/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.patch b/FileSets/PatchSource/dbus_systemcalc.py-v3.50.patch
similarity index 99%
rename from FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.patch
rename to FileSets/PatchSource/dbus_systemcalc.py-v3.50.patch
index 02a60095..04f18df0 100644
--- a/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.patch
+++ b/FileSets/PatchSource/dbus_systemcalc.py-v3.50.patch
@@ -1,5 +1,5 @@
---- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22.orig 2024-09-13 09:15:41
-+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.50~22 2024-09-17 15:22:37
+--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.50.orig 2024-09-13 09:15:41
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/dbus_systemcalc.py-v3.50 2024-10-01 15:26:58
@@ -1,6 +1,8 @@
#!/usr/bin/python3 -u
# -*- coding: utf-8 -*-
diff --git a/FileSets/PatchSource/main.qml-v3.50~22 b/FileSets/PatchSource/main.qml-v3.50
similarity index 83%
rename from FileSets/PatchSource/main.qml-v3.50~22
rename to FileSets/PatchSource/main.qml-v3.50
index 85b36eb2..93ed5f8c 100644
--- a/FileSets/PatchSource/main.qml-v3.50~22
+++ b/FileSets/PatchSource/main.qml-v3.50
@@ -11,7 +11,7 @@ PageStackWindow {
id: rootWindow
gpsConnected: gpsFix.value === 1
- onCompletedChanged: checkAlarm()
+ onCompletedChanged: { checkAlarm(); showNewUiPopup() }
initialPage: PageMain {}
property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" }
@@ -19,17 +19,17 @@ PageStackWindow {
property bool completed: false
property bool alarm: alarmNotification.valid ? alarmNotification.value : 0
property bool showAlert: alertNotification.valid ? alertNotification.value : 0
-//////// added for GuiMods flow pages
- property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid
+//////// modified for GuiMods flow pages
+ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.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";
- /////////////////////property bool hasGridMeter: theSystem.hasGridMeter
property bool hasVebusEss: ['ESS', 'Hub-4'].indexOf(theSystem.systemType.value) > -1
property bool showInputLoads: theSystem.acInLoad.power.valid && (hasVebusEss ? (theSystem.hasGridMeter && withoutGridMeter.value === 0) : theSystem.hasGridMeter)
+ property int newUiAnnouncementVersion: 2 // Increase to make the popup appear again
//////// modified for GuiMods pages
property string currentHubOverview: "OverviewHub.qml"
@@ -95,19 +95,19 @@ PageStackWindow {
}
}
- VBusItem {
- id: generatorOverview
- bind: "com.victronenergy.settings/Settings/Relay/Function"
- onValueChanged: selectGeneratorOverview ()
- }
-
-////// GuiMods
VBusItem
{
id: generatorOverviewEnhanced
bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview"
onValueChanged: selectGeneratorOverview ()
}
+////// end GuiMods
+
+ VBusItem {
+ id: generatorOverview
+ bind: "com.victronenergy.settings/Settings/Relay/Function"
+ onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1)
+ }
VBusItem {
bind: "com.victronenergy.generator.startstop1/GensetProductId"
@@ -117,7 +117,7 @@ PageStackWindow {
// 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) {
@@ -153,7 +153,7 @@ PageStackWindow {
}
}
-//////// handle OverviewMobileEnhanced page
+//////// GuiMods handle OverviewMobileEnhanced page
VBusItem
{
id: mobileOverview
@@ -218,13 +218,13 @@ PageStackWindow {
onValueChanged: extraOverview ("OverviewTanksTempsDigInputs.qml", value === 1)
}
- VBusItem {
- id: tanksOverview
- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview"
- onValueChanged:{
- extraOverview("OverviewTanks.qml", value === 1)
- }
- }
+ VBusItem {
+ id: tanksOverview
+ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview"
+ onValueChanged:{
+ extraOverview("OverviewTanks.qml", value === 1)
+ }
+ }
VBusItem {
id: startWithMenu
@@ -261,6 +261,11 @@ PageStackWindow {
bind: "com.victronenergy.platform/Notifications/Alarm"
}
+ VBusItem {
+ id: newUiAnnouncement
+ bind: "com.victronenergy.settings/Settings/Gui/NewGuiLastAnnouncement"
+ }
+
// 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.
@@ -353,73 +358,76 @@ PageStackWindow {
}
}
- Item {
- anchors.verticalCenter: parent.verticalCenter
- height: mbTools.height
- width: mbTools.width - pagesItem.width - menusItem.width - centerScrollIndicator.width
+ 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)
+ }
+ }
- MouseArea
- {
- anchors.fill: parent
- onClicked:
- {
- if (darkModeItem.valid)
- darkModeItem.setValue (! darkMode)
- }
+ Row {
+ anchors.centerIn: parent
+
+ MbIcon {
+ anchors.verticalCenter: parent.verticalCenter
+ iconId: pageStack.currentPage ? pageStack.currentPage.leftIcon : ""
}
- Text
- {
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- text: qsTr ("change to") + "\n" + (darkMode ? qsTr ("Light mode") : qsTr ("Dark mode"))
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: pageStack.currentPage ? pageStack.currentPage.leftText : ""
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 : ""
+ font.pixelSize: 16
}
}
+ }
- Item {
- id: menusItem
- anchors.verticalCenter: parent.verticalCenter
- height: mbTools.height
- width: pagesItem.width
+ MbIcon {
+ id: centerScrollIndicator
- MouseArea {
- anchors.fill: parent
- onClicked: {
- if (pageStack.currentPage)
- pageStack.currentPage.toolbarHandler.rightAction(true)
- }
+ 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
+ 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
}
}
}
@@ -456,6 +464,13 @@ PageStackWindow {
Timer {
interval: 2000
running: completed && overviewsLoaded && startWithMenu.valid
+ onTriggered: if (startWithMenu.value === 0) showOverview()
+ }
+
+ Timer {
+ id: newUiPopupTimer
+ interval: 10000
+ running: false
onTriggered:
{
//// GuiMods - modified for OverviewGridParallelEnhanced page
@@ -464,6 +479,14 @@ PageStackWindow {
}
}
+ function showNewUiPopup()
+ {
+ var announce = newUiAnnouncement.value < newUiAnnouncementVersion
+ if (announce && vePlatform.isGuiv2Installed() && vePlatform.displayPresent()) {
+ newUiPopupTimer.start()
+ }
+ }
+
function getDefaultOverviewIndex()
{
if(!defaultOverview.valid)
@@ -484,7 +507,11 @@ PageStackWindow {
Component {
id: offlineFwUpdates
PageSettingsFirmwareOffline { checkOnCompleted: true}
+ }
+ Component {
+ id: popupNewUi
+ PopupNewUi { version: newUiAnnouncementVersion }
}
// Add or remove extra overviews. for example, generator overview
@@ -530,6 +557,7 @@ PageStackWindow {
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
diff --git a/FileSets/PatchSource/main.qml-v3.50~22.orig b/FileSets/PatchSource/main.qml-v3.50.orig
similarity index 93%
rename from FileSets/PatchSource/main.qml-v3.50~22.orig
rename to FileSets/PatchSource/main.qml-v3.50.orig
index 751bc77c..41440a44 100644
--- a/FileSets/PatchSource/main.qml-v3.50~22.orig
+++ b/FileSets/PatchSource/main.qml-v3.50.orig
@@ -8,7 +8,7 @@ PageStackWindow {
id: rootWindow
gpsConnected: gpsFix.value === 1
- onCompletedChanged: checkAlarm()
+ onCompletedChanged: { checkAlarm(); showNewUiPopup() }
initialPage: PageMain {}
property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" }
@@ -25,6 +25,7 @@ PageStackWindow {
property bool hasVebusEss: ['ESS', 'Hub-4'].indexOf(theSystem.systemType.value) > -1
property bool showInputLoads: theSystem.acInLoad.power.valid && (hasVebusEss ? (theSystem.hasGridMeter && withoutGridMeter.value === 0) : theSystem.hasGridMeter)
+ property int newUiAnnouncementVersion: 2 // Increase to make the popup appear again
// Keep track of the current view (menu/overview) to show as default next time the
// CCGX is restarted
@@ -112,6 +113,11 @@ PageStackWindow {
bind: "com.victronenergy.platform/Notifications/Alarm"
}
+ VBusItem {
+ id: newUiAnnouncement
+ bind: "com.victronenergy.settings/Settings/Gui/NewGuiLastAnnouncement"
+ }
+
// 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.
@@ -274,6 +280,21 @@ PageStackWindow {
onTriggered: if (startWithMenu.value === 0) showOverview()
}
+ Timer {
+ id: newUiPopupTimer
+ interval: 10000
+ running: false
+ onTriggered: rootWindow.pageStack.push(popupNewUi)
+ }
+
+ function showNewUiPopup()
+ {
+ var announce = newUiAnnouncement.value < newUiAnnouncementVersion
+ if (announce && vePlatform.isGuiv2Installed() && vePlatform.displayPresent()) {
+ newUiPopupTimer.start()
+ }
+ }
+
function getDefaultOverviewIndex()
{
if(!defaultOverview.valid)
@@ -296,6 +317,11 @@ PageStackWindow {
PageSettingsFirmwareOffline { checkOnCompleted: true}
}
+ Component {
+ id: popupNewUi
+ PopupNewUi { version: newUiAnnouncementVersion }
+ }
+
// 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.
diff --git a/FileSets/PatchSource/main.qml-v3.50~22.patch b/FileSets/PatchSource/main.qml-v3.50.patch
similarity index 60%
rename from FileSets/PatchSource/main.qml-v3.50~22.patch
rename to FileSets/PatchSource/main.qml-v3.50.patch
index cce4b1b5..7556bffa 100644
--- a/FileSets/PatchSource/main.qml-v3.50~22.patch
+++ b/FileSets/PatchSource/main.qml-v3.50.patch
@@ -1,5 +1,5 @@
---- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.50~22.orig 2024-09-17 15:44:02
-+++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.50~22 2024-09-17 15:44:06
+--- /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.50.orig 2024-10-11 05:51:41
++++ /Users/Kevin/GitHub/GuiMods.copy/FileSets/PatchSource/main.qml-v3.50 2024-10-12 11:57:19
@@ -1,3 +1,6 @@
+//////// Modified to hide the OverviewTiles page
+//////// Modified to substitute flow overview pages
@@ -7,22 +7,19 @@
import QtQuick 1.1
import Qt.labs.components.native 1.0
-@@ -16,35 +19,96 @@
+@@ -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 && tanksOverview.valid && startWithMenu.valid && mobileOverviewEnhanced.valid && guiModsFlowOverview.valid && generatorOverviewEnhanced.valid
++//////// modified for GuiMods flow pages
++ property bool overviewsLoaded: defaultOverview.valid && generatorOverview.valid && mobileOverview.valid && tanksOverview.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";
-
-+ /////////////////////property bool hasGridMeter: theSystem.hasGridMeter
- property bool hasVebusEss: ['ESS', 'Hub-4'].indexOf(theSystem.systemType.value) > -1
+@@ -27,19 +31,78 @@
property bool showInputLoads: theSystem.acInLoad.power.valid && (hasVebusEss ? (theSystem.hasGridMeter && withoutGridMeter.value === 0) : theSystem.hasGridMeter)
+ property int newUiAnnouncementVersion: 2 // Increase to make the popup appear again
+//////// modified for GuiMods pages
+ property string currentHubOverview: "OverviewHub.qml"
@@ -93,34 +90,18 @@
+ }
+ }
+
- VBusItem {
- id: generatorOverview
- bind: "com.victronenergy.settings/Settings/Relay/Function"
-- onValueChanged: extraOverview("OverviewGeneratorRelay.qml", value === 1)
-+ onValueChanged: selectGeneratorOverview ()
- }
-
-+////// GuiMods
+ VBusItem
+ {
+ id: generatorOverviewEnhanced
+ bind: "com.victronenergy.settings/Settings/GuiMods/UseEnhancedGeneratorOverview"
+ onValueChanged: selectGeneratorOverview ()
+ }
++////// end GuiMods
+
VBusItem {
- bind: "com.victronenergy.generator.startstop1/GensetProductId"
- onValueChanged: {
-@@ -53,7 +117,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) {
-@@ -62,21 +126,106 @@
+ id: generatorOverview
+ bind: "com.victronenergy.settings/Settings/Relay/Function"
+@@ -63,13 +126,98 @@
}
}
@@ -156,15 +137,8 @@
+ }
+ }
}
-- VBusItem {
-- id: tanksOverview
-- bind: "com.victronenergy.settings/Settings/Gui/TanksOverview"
-- onValueChanged:{
-- extraOverview("OverviewTanks.qml", value === 1)
-- }
-- }
-
-+//////// handle OverviewMobileEnhanced page
++
++//////// GuiMods handle OverviewMobileEnhanced page
+ VBusItem
+ {
+ id: mobileOverview
@@ -228,27 +202,15 @@
+ 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"
-@@ -166,76 +315,111 @@
+ id: tanksOverview
+ bind: "com.victronenergy.settings/Settings/Gui/TanksOverview"
+@@ -171,7 +319,45 @@
+ ToolBarLayout {
id: mbTools
height: parent.height
-
-- Item {
-- anchors.verticalCenter: parent.verticalCenter
-- anchors.left: mbTools.left
-- height: mbTools.height
-- width: 200
++
+//// GuiMods - DarkMode
+ Row
+ {
@@ -260,18 +222,13 @@
+ 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
@@ -290,122 +247,12 @@
+ 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
-+ }
- }
- }
- }
-@@ -250,9 +434,10 @@
+ Item {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: mbTools.left
+@@ -256,9 +442,10 @@
ListElement {
pageSource: "OverviewHub.qml"
}
@@ -419,11 +266,11 @@
}
Component {
-@@ -271,7 +456,12 @@
- Timer {
- interval: 2000
- running: completed && overviewsLoaded && startWithMenu.valid
-- onTriggered: if (startWithMenu.value === 0) showOverview()
+@@ -284,7 +471,12 @@
+ id: newUiPopupTimer
+ interval: 10000
+ running: false
+- onTriggered: rootWindow.pageStack.push(popupNewUi)
+ onTriggered:
+ {
+//// GuiMods - modified for OverviewGridParallelEnhanced page
@@ -432,16 +279,8 @@
+ }
}
- function getDefaultOverviewIndex()
-@@ -294,6 +484,7 @@
- Component {
- id: offlineFwUpdates
- PageSettingsFirmwareOffline { checkOnCompleted: true}
-+
- }
-
- // Add or remove extra overviews. for example, generator overview
-@@ -324,11 +515,19 @@
+ function showNewUiPopup()
+@@ -350,13 +542,22 @@
}
}
@@ -460,4 +299,7 @@
+ 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
diff --git a/FileSets/v3.10/ObjectAcConnection.qml b/FileSets/v3.10/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.10/ObjectAcConnection.qml
+++ b/FileSets/v3.10/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/OverviewAcValuesEnhanced.qml b/FileSets/v3.10/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.10/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.10/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.10/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.10/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.10/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/OverviewMobileEnhanced.qml b/FileSets/v3.10/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.10/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.10/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/OverviewTanksTempsDigInputs.qml b/FileSets/v3.10/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.10/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.10/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/TileDigIn.qml b/FileSets/v3.10/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.10/TileDigIn.qml
+++ b/FileSets/v3.10/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/TileRelay.qml b/FileSets/v3.10/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.10/TileRelay.qml
+++ b/FileSets/v3.10/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.10/dbus_digitalinputs.py b/FileSets/v3.10/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.10/dbus_digitalinputs.py
+++ b/FileSets/v3.10/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.11/ObjectAcConnection.qml b/FileSets/v3.11/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.11/ObjectAcConnection.qml
+++ b/FileSets/v3.11/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/OverviewAcValuesEnhanced.qml b/FileSets/v3.11/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.11/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.11/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.11/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.11/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.11/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/OverviewMobileEnhanced.qml b/FileSets/v3.11/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.11/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.11/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/OverviewTanksTempsDigInputs.qml b/FileSets/v3.11/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.11/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.11/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/TileDigIn.qml b/FileSets/v3.11/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.11/TileDigIn.qml
+++ b/FileSets/v3.11/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/TileRelay.qml b/FileSets/v3.11/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.11/TileRelay.qml
+++ b/FileSets/v3.11/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.11/dbus_digitalinputs.py b/FileSets/v3.11/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.11/dbus_digitalinputs.py
+++ b/FileSets/v3.11/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.12/ObjectAcConnection.qml b/FileSets/v3.12/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.12/ObjectAcConnection.qml
+++ b/FileSets/v3.12/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/OverviewAcValuesEnhanced.qml b/FileSets/v3.12/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.12/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.12/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.12/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.12/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.12/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/OverviewMobileEnhanced.qml b/FileSets/v3.12/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.12/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.12/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/OverviewTanksTempsDigInputs.qml b/FileSets/v3.12/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.12/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.12/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/TileDigIn.qml b/FileSets/v3.12/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.12/TileDigIn.qml
+++ b/FileSets/v3.12/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/TileRelay.qml b/FileSets/v3.12/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.12/TileRelay.qml
+++ b/FileSets/v3.12/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.12/dbus_digitalinputs.py b/FileSets/v3.12/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.12/dbus_digitalinputs.py
+++ b/FileSets/v3.12/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.13/ObjectAcConnection.qml b/FileSets/v3.13/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.13/ObjectAcConnection.qml
+++ b/FileSets/v3.13/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/OverviewAcValuesEnhanced.qml b/FileSets/v3.13/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.13/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.13/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.13/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.13/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.13/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/OverviewMobileEnhanced.qml b/FileSets/v3.13/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.13/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.13/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/OverviewTanksTempsDigInputs.qml b/FileSets/v3.13/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.13/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.13/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/TileDigIn.qml b/FileSets/v3.13/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.13/TileDigIn.qml
+++ b/FileSets/v3.13/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/TileRelay.qml b/FileSets/v3.13/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.13/TileRelay.qml
+++ b/FileSets/v3.13/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.13/dbus_digitalinputs.py b/FileSets/v3.13/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.13/dbus_digitalinputs.py
+++ b/FileSets/v3.13/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.14/ObjectAcConnection.qml b/FileSets/v3.14/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.14/ObjectAcConnection.qml
+++ b/FileSets/v3.14/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/OverviewAcValuesEnhanced.qml b/FileSets/v3.14/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.14/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.14/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.14/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.14/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.14/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/OverviewMobileEnhanced.qml b/FileSets/v3.14/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.14/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.14/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/OverviewTanksTempsDigInputs.qml b/FileSets/v3.14/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.14/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.14/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/TileDigIn.qml b/FileSets/v3.14/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.14/TileDigIn.qml
+++ b/FileSets/v3.14/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/TileRelay.qml b/FileSets/v3.14/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.14/TileRelay.qml
+++ b/FileSets/v3.14/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.14/dbus_digitalinputs.py b/FileSets/v3.14/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.14/dbus_digitalinputs.py
+++ b/FileSets/v3.14/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.20/ObjectAcConnection.qml b/FileSets/v3.20/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.20/ObjectAcConnection.qml
+++ b/FileSets/v3.20/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/OverviewAcValuesEnhanced.qml b/FileSets/v3.20/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.20/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.20/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.20/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.20/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.20/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/OverviewMobileEnhanced.qml b/FileSets/v3.20/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.20/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.20/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/OverviewTanksTempsDigInputs.qml b/FileSets/v3.20/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.20/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.20/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/PageSettingsGenerator.qml b/FileSets/v3.20/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.20/PageSettingsGenerator.qml
+++ b/FileSets/v3.20/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/TileDigIn.qml b/FileSets/v3.20/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.20/TileDigIn.qml
+++ b/FileSets/v3.20/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/TileRelay.qml b/FileSets/v3.20/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.20/TileRelay.qml
+++ b/FileSets/v3.20/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.20/dbus_digitalinputs.py b/FileSets/v3.20/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.20/dbus_digitalinputs.py
+++ b/FileSets/v3.20/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.21/ObjectAcConnection.qml b/FileSets/v3.21/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.21/ObjectAcConnection.qml
+++ b/FileSets/v3.21/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/OverviewAcValuesEnhanced.qml b/FileSets/v3.21/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.21/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.21/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.21/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.21/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.21/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/OverviewMobileEnhanced.qml b/FileSets/v3.21/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.21/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.21/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/OverviewTanksTempsDigInputs.qml b/FileSets/v3.21/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.21/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.21/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/PageSettingsGenerator.qml b/FileSets/v3.21/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.21/PageSettingsGenerator.qml
+++ b/FileSets/v3.21/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/TileDigIn.qml b/FileSets/v3.21/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.21/TileDigIn.qml
+++ b/FileSets/v3.21/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/TileRelay.qml b/FileSets/v3.21/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.21/TileRelay.qml
+++ b/FileSets/v3.21/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.21/dbus_digitalinputs.py b/FileSets/v3.21/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.21/dbus_digitalinputs.py
+++ b/FileSets/v3.21/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.22/ObjectAcConnection.qml b/FileSets/v3.22/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.22/ObjectAcConnection.qml
+++ b/FileSets/v3.22/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/OverviewAcValuesEnhanced.qml b/FileSets/v3.22/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.22/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.22/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.22/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.22/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.22/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/OverviewMobileEnhanced.qml b/FileSets/v3.22/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.22/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.22/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/OverviewTanksTempsDigInputs.qml b/FileSets/v3.22/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.22/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.22/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/PageSettingsGenerator.qml b/FileSets/v3.22/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.22/PageSettingsGenerator.qml
+++ b/FileSets/v3.22/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/TileDigIn.qml b/FileSets/v3.22/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.22/TileDigIn.qml
+++ b/FileSets/v3.22/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/TileRelay.qml b/FileSets/v3.22/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.22/TileRelay.qml
+++ b/FileSets/v3.22/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.22/dbus_digitalinputs.py b/FileSets/v3.22/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.22/dbus_digitalinputs.py
+++ b/FileSets/v3.22/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.30/ObjectAcConnection.qml b/FileSets/v3.30/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.30/ObjectAcConnection.qml
+++ b/FileSets/v3.30/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/OverviewAcValuesEnhanced.qml b/FileSets/v3.30/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.30/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.30/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.30/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.30/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.30/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/OverviewMobileEnhanced.qml b/FileSets/v3.30/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.30/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.30/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/OverviewTanksTempsDigInputs.qml b/FileSets/v3.30/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.30/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.30/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/PageSettingsGenerator.qml b/FileSets/v3.30/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.30/PageSettingsGenerator.qml
+++ b/FileSets/v3.30/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/PageSettingsRelay.qml b/FileSets/v3.30/PageSettingsRelay.qml
index 41872500..c0f31c7d 120000
--- a/FileSets/v3.30/PageSettingsRelay.qml
+++ b/FileSets/v3.30/PageSettingsRelay.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
+../v3.41/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/TileDigIn.qml b/FileSets/v3.30/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.30/TileDigIn.qml
+++ b/FileSets/v3.30/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/TileRelay.qml b/FileSets/v3.30/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.30/TileRelay.qml
+++ b/FileSets/v3.30/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.30/dbus_digitalinputs.py b/FileSets/v3.30/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.30/dbus_digitalinputs.py
+++ b/FileSets/v3.30/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.31/ObjectAcConnection.qml b/FileSets/v3.31/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.31/ObjectAcConnection.qml
+++ b/FileSets/v3.31/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/OverviewAcValuesEnhanced.qml b/FileSets/v3.31/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.31/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.31/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.31/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.31/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.31/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/OverviewMobileEnhanced.qml b/FileSets/v3.31/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.31/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.31/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/OverviewTanksTempsDigInputs.qml b/FileSets/v3.31/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.31/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.31/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/PageSettingsGenerator.qml b/FileSets/v3.31/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.31/PageSettingsGenerator.qml
+++ b/FileSets/v3.31/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/PageSettingsRelay.qml b/FileSets/v3.31/PageSettingsRelay.qml
index 41872500..c0f31c7d 120000
--- a/FileSets/v3.31/PageSettingsRelay.qml
+++ b/FileSets/v3.31/PageSettingsRelay.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
+../v3.41/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/TileDigIn.qml b/FileSets/v3.31/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.31/TileDigIn.qml
+++ b/FileSets/v3.31/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/TileRelay.qml b/FileSets/v3.31/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.31/TileRelay.qml
+++ b/FileSets/v3.31/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.31/dbus_digitalinputs.py b/FileSets/v3.31/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.31/dbus_digitalinputs.py
+++ b/FileSets/v3.31/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.33/ObjectAcConnection.qml b/FileSets/v3.33/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.33/ObjectAcConnection.qml
+++ b/FileSets/v3.33/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/OverviewAcValuesEnhanced.qml b/FileSets/v3.33/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.33/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.33/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.33/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.33/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.33/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/OverviewMobileEnhanced.qml b/FileSets/v3.33/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.33/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.33/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/OverviewTanksTempsDigInputs.qml b/FileSets/v3.33/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.33/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.33/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/PageSettingsGenerator.qml b/FileSets/v3.33/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.33/PageSettingsGenerator.qml
+++ b/FileSets/v3.33/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/PageSettingsRelay.qml b/FileSets/v3.33/PageSettingsRelay.qml
index 41872500..c0f31c7d 120000
--- a/FileSets/v3.33/PageSettingsRelay.qml
+++ b/FileSets/v3.33/PageSettingsRelay.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
+../v3.41/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/TileDigIn.qml b/FileSets/v3.33/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.33/TileDigIn.qml
+++ b/FileSets/v3.33/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/TileRelay.qml b/FileSets/v3.33/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.33/TileRelay.qml
+++ b/FileSets/v3.33/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.33/dbus_digitalinputs.py b/FileSets/v3.33/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.33/dbus_digitalinputs.py
+++ b/FileSets/v3.33/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.34/ObjectAcConnection.qml b/FileSets/v3.34/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.34/ObjectAcConnection.qml
+++ b/FileSets/v3.34/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/OverviewAcValuesEnhanced.qml b/FileSets/v3.34/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.34/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.34/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.34/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.34/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.34/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/OverviewMobileEnhanced.qml b/FileSets/v3.34/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.34/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.34/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/OverviewTanksTempsDigInputs.qml b/FileSets/v3.34/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.34/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.34/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/PageSettingsGenerator.qml b/FileSets/v3.34/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.34/PageSettingsGenerator.qml
+++ b/FileSets/v3.34/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/PageSettingsRelay.qml b/FileSets/v3.34/PageSettingsRelay.qml
index 41872500..c0f31c7d 120000
--- a/FileSets/v3.34/PageSettingsRelay.qml
+++ b/FileSets/v3.34/PageSettingsRelay.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
+../v3.41/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/TileDigIn.qml b/FileSets/v3.34/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.34/TileDigIn.qml
+++ b/FileSets/v3.34/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/TileRelay.qml b/FileSets/v3.34/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.34/TileRelay.qml
+++ b/FileSets/v3.34/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.34/dbus_digitalinputs.py b/FileSets/v3.34/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.34/dbus_digitalinputs.py
+++ b/FileSets/v3.34/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.40/DetailAcInput.qml b/FileSets/v3.40/DetailAcInput.qml
index cadafa4e..66967128 120000
--- a/FileSets/v3.40/DetailAcInput.qml
+++ b/FileSets/v3.40/DetailAcInput.qml
@@ -1 +1 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
+../v3.51~2/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/DetailInverter.qml b/FileSets/v3.40/DetailInverter.qml
index 8a637d73..674958be 120000
--- a/FileSets/v3.40/DetailInverter.qml
+++ b/FileSets/v3.40/DetailInverter.qml
@@ -1 +1 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
+../v3.51~2/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/DetailLoadsCombined.qml b/FileSets/v3.40/DetailLoadsCombined.qml
index 5775b2f4..f51dfec1 120000
--- a/FileSets/v3.40/DetailLoadsCombined.qml
+++ b/FileSets/v3.40/DetailLoadsCombined.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/DetailLoadsOnInput.qml b/FileSets/v3.40/DetailLoadsOnInput.qml
index 8486816b..4813eff7 120000
--- a/FileSets/v3.40/DetailLoadsOnInput.qml
+++ b/FileSets/v3.40/DetailLoadsOnInput.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/DetailLoadsOnOutput.qml b/FileSets/v3.40/DetailLoadsOnOutput.qml
index 5aba1991..746a0773 120000
--- a/FileSets/v3.40/DetailLoadsOnOutput.qml
+++ b/FileSets/v3.40/DetailLoadsOnOutput.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/HubData.qml b/FileSets/v3.40/HubData.qml
index c7e782ee..4321b049 120000
--- a/FileSets/v3.40/HubData.qml
+++ b/FileSets/v3.40/HubData.qml
@@ -1 +1 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
+../v3.51~2/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/ObjectAcConnection.qml b/FileSets/v3.40/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.40/ObjectAcConnection.qml
+++ b/FileSets/v3.40/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewAcValuesEnhanced.qml b/FileSets/v3.40/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.40/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.40/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewFlowComplex.qml b/FileSets/v3.40/OverviewFlowComplex.qml
index b8f9a5f4..117e095b 120000
--- a/FileSets/v3.40/OverviewFlowComplex.qml
+++ b/FileSets/v3.40/OverviewFlowComplex.qml
@@ -1 +1 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
+../v3.51~2/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.40/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.40/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.40/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewGridParallel.qml b/FileSets/v3.40/OverviewGridParallel.qml
index 7af3834a..3210810c 120000
--- a/FileSets/v3.40/OverviewGridParallel.qml
+++ b/FileSets/v3.40/OverviewGridParallel.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
+../v3.51~2/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewHub.qml b/FileSets/v3.40/OverviewHub.qml
index 88267085..1bfb809c 120000
--- a/FileSets/v3.40/OverviewHub.qml
+++ b/FileSets/v3.40/OverviewHub.qml
@@ -1 +1 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
+../v3.51~2/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewHubEnhanced.qml b/FileSets/v3.40/OverviewHubEnhanced.qml
index 2e238e43..0ee6c316 120000
--- a/FileSets/v3.40/OverviewHubEnhanced.qml
+++ b/FileSets/v3.40/OverviewHubEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewMobileEnhanced.qml b/FileSets/v3.40/OverviewMobileEnhanced.qml
index 69b3f0d9..bde2f4f3 120000
--- a/FileSets/v3.40/OverviewMobileEnhanced.qml
+++ b/FileSets/v3.40/OverviewMobileEnhanced.qml
@@ -1 +1 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
+../v3.41/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/OverviewTanksTempsDigInputs.qml b/FileSets/v3.40/OverviewTanksTempsDigInputs.qml
index 4fa05bdf..e3d77e65 120000
--- a/FileSets/v3.40/OverviewTanksTempsDigInputs.qml
+++ b/FileSets/v3.40/OverviewTanksTempsDigInputs.qml
@@ -1 +1 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
+../v3.41/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/PageSettingsGenerator.qml b/FileSets/v3.40/PageSettingsGenerator.qml
index 5e12924e..5a35eab3 120000
--- a/FileSets/v3.40/PageSettingsGenerator.qml
+++ b/FileSets/v3.40/PageSettingsGenerator.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
+../v3.41/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/PageSettingsRelay.qml b/FileSets/v3.40/PageSettingsRelay.qml
index 41872500..c0f31c7d 120000
--- a/FileSets/v3.40/PageSettingsRelay.qml
+++ b/FileSets/v3.40/PageSettingsRelay.qml
@@ -1 +1 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
+../v3.41/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/PowerGauge.qml b/FileSets/v3.40/PowerGauge.qml
index 452ca646..8c1f5473 120000
--- a/FileSets/v3.40/PowerGauge.qml
+++ b/FileSets/v3.40/PowerGauge.qml
@@ -1 +1 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
+../v3.51~2/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/TileDigIn.qml b/FileSets/v3.40/TileDigIn.qml
index f11f1206..8c8b783a 120000
--- a/FileSets/v3.40/TileDigIn.qml
+++ b/FileSets/v3.40/TileDigIn.qml
@@ -1 +1 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
+../v3.41/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/TileRelay.qml b/FileSets/v3.40/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.40/TileRelay.qml
+++ b/FileSets/v3.40/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.40/dbus_digitalinputs.py b/FileSets/v3.40/dbus_digitalinputs.py
index 717bc909..6d87f8e8 120000
--- a/FileSets/v3.40/dbus_digitalinputs.py
+++ b/FileSets/v3.40/dbus_digitalinputs.py
@@ -1 +1 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
+../v3.41/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.40/startstop.py b/FileSets/v3.40/startstop.py
index 42f29aec..c3c07120 120000
--- a/FileSets/v3.40/startstop.py
+++ b/FileSets/v3.40/startstop.py
@@ -1 +1 @@
-../v3.50~22/startstop.py
\ No newline at end of file
+../v3.41/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.41/DetailAcInput.qml b/FileSets/v3.41/DetailAcInput.qml
index cadafa4e..66967128 120000
--- a/FileSets/v3.41/DetailAcInput.qml
+++ b/FileSets/v3.41/DetailAcInput.qml
@@ -1 +1 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
+../v3.51~2/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/DetailInverter.qml b/FileSets/v3.41/DetailInverter.qml
index 8a637d73..674958be 120000
--- a/FileSets/v3.41/DetailInverter.qml
+++ b/FileSets/v3.41/DetailInverter.qml
@@ -1 +1 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
+../v3.51~2/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/DetailLoadsCombined.qml b/FileSets/v3.41/DetailLoadsCombined.qml
index 5775b2f4..f51dfec1 120000
--- a/FileSets/v3.41/DetailLoadsCombined.qml
+++ b/FileSets/v3.41/DetailLoadsCombined.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/DetailLoadsOnInput.qml b/FileSets/v3.41/DetailLoadsOnInput.qml
index 8486816b..4813eff7 120000
--- a/FileSets/v3.41/DetailLoadsOnInput.qml
+++ b/FileSets/v3.41/DetailLoadsOnInput.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/DetailLoadsOnOutput.qml b/FileSets/v3.41/DetailLoadsOnOutput.qml
index 5aba1991..746a0773 120000
--- a/FileSets/v3.41/DetailLoadsOnOutput.qml
+++ b/FileSets/v3.41/DetailLoadsOnOutput.qml
@@ -1 +1 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
+../v3.51~2/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/HubData.qml b/FileSets/v3.41/HubData.qml
index c7e782ee..4321b049 120000
--- a/FileSets/v3.41/HubData.qml
+++ b/FileSets/v3.41/HubData.qml
@@ -1 +1 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
+../v3.51~2/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/ObjectAcConnection.qml b/FileSets/v3.41/ObjectAcConnection.qml
index 2ca36f5e..a59b2c6d 120000
--- a/FileSets/v3.41/ObjectAcConnection.qml
+++ b/FileSets/v3.41/ObjectAcConnection.qml
@@ -1 +1 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewAcValuesEnhanced.qml b/FileSets/v3.41/OverviewAcValuesEnhanced.qml
index 2fcae9eb..06278feb 120000
--- a/FileSets/v3.41/OverviewAcValuesEnhanced.qml
+++ b/FileSets/v3.41/OverviewAcValuesEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewFlowComplex.qml b/FileSets/v3.41/OverviewFlowComplex.qml
index b8f9a5f4..117e095b 120000
--- a/FileSets/v3.41/OverviewFlowComplex.qml
+++ b/FileSets/v3.41/OverviewFlowComplex.qml
@@ -1 +1 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
+../v3.51~2/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.41/OverviewGeneratorRelayEnhanced.qml
index 65979936..e4fe32d3 120000
--- a/FileSets/v3.41/OverviewGeneratorRelayEnhanced.qml
+++ b/FileSets/v3.41/OverviewGeneratorRelayEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewGridParallel.qml b/FileSets/v3.41/OverviewGridParallel.qml
index 7af3834a..3210810c 120000
--- a/FileSets/v3.41/OverviewGridParallel.qml
+++ b/FileSets/v3.41/OverviewGridParallel.qml
@@ -1 +1 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
+../v3.51~2/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewHub.qml b/FileSets/v3.41/OverviewHub.qml
index 88267085..1bfb809c 120000
--- a/FileSets/v3.41/OverviewHub.qml
+++ b/FileSets/v3.41/OverviewHub.qml
@@ -1 +1 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
+../v3.51~2/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewHubEnhanced.qml b/FileSets/v3.41/OverviewHubEnhanced.qml
index 2e238e43..0ee6c316 120000
--- a/FileSets/v3.41/OverviewHubEnhanced.qml
+++ b/FileSets/v3.41/OverviewHubEnhanced.qml
@@ -1 +1 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
+../v3.51~2/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewMobileEnhanced.qml b/FileSets/v3.41/OverviewMobileEnhanced.qml
deleted file mode 120000
index 69b3f0d9..00000000
--- a/FileSets/v3.41/OverviewMobileEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~5/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewMobileEnhanced.qml b/FileSets/v3.41/OverviewMobileEnhanced.qml
new file mode 100644
index 00000000..175a4bac
--- /dev/null
+++ b/FileSets/v3.41/OverviewMobileEnhanced.qml
@@ -0,0 +1,989 @@
+// GuiMods Enhancements to OverviewMobile screen
+
+// Removed logo and added AC INPUT and SYSTEM tiles originally displayed on other overviews
+// Added voltage, current and frequency to AC INPUT and AC LOADS tiles
+// Added source (Grid, Generator, Shore Power) to AC INPUT tile
+// Replaced to/from battery with current in DC SYSTEM tile
+// DC SYSTEM tile title now reflects direction: "DC LOADS, DC CHARGER"
+// Rearranged tiles to match a left to right signal flow : sources on left, loads on right
+// Standardized "info" tile sizes to 1 or 1.5 wide x 1 or 2 high
+// infoArea defines usable space for info tiles and all tiles are a child of infoArea
+// (makes repositioning easier than when they were in separate column objects)
+// Large text for main paremeter in each tile has been reduced in size to allow more parameters without
+// expanding tile height (30 to 22)
+// merged SYSTEM and STATUS tiles
+// removed speed from STATUS to reduce tile height
+// hide "reason" text if it's blank to save space
+// changed clock to 12-hour format
+// Capitialized battery state: "Idle", "Charging", "Discharging"
+// errors and notificaitons in SYSTEM/STATUS tile may push clock off bottom of tile
+// Tile content for items that are not present are made invisible - tile remains in place
+// that is no height adjustments when a tile provides no information
+// Adjust button widths so that pump button fits within tank column
+// Hide pump button when not enabled giving more room for tanks
+// Add temperature sensors to tanks column
+// add control of VE.Direct inverters
+
+// Includes changes to handle SeeLevel NMEA2000 tank sensor:
+// Ignore the real incoming tank dBus service because it's information changes
+// Changes in TileText.qml are also part of the TankRepeater package
+
+// Search for //////// to find changes
+
+import QtQuick 1.1
+import com.victron.velib 1.0
+import "utils.js" as Utils
+import "timeToGo.js" as TTG
+import "enhancedFormat.js" as EnhFmt
+
+OverviewPage {
+ title: qsTr("Mobile")
+ id: root
+
+ property color detailColor: "#b3b3b3"
+ property real touchTargetOpacity: 0.3
+ property int touchArea: 40
+
+ property variant sys: theSystem
+ property string settingsBindPreffix: "com.victronenergy.settings"
+ property string pumpBindPreffix: "com.victronenergy.pump.startstop0"
+ property variant activeNotifications: NotificationCenter.notifications.filter(
+ function isActive(obj) { return obj.active} )
+ property string noAdjustableByDmc: qsTr("This setting is disabled when a Digital Multi Control " +
+ "is connected. If it was recently disconnected execute " +
+ "\"Redetect system\" that is available on the inverter menu page.")
+ property string noAdjustableByBms: qsTr("This setting is disabled when a VE.Bus BMS " +
+ "is connected. If it was recently disconnected execute " +
+ "\"Redetect system\" that is available on the inverter menu page.")
+ property string noAdjustableTextByConfig: qsTr("This setting is disabled. " +
+ "Possible reasons are \"Overruled by remote\" is not enabled or " +
+ "an assistant is preventing the adjustment. Please, check " +
+ "the inverter configuration with VEConfigure.")
+
+//////// added to keep track of tanks and temps
+ property int numberOfTemps: 0
+ property int tankTempCount: tankModel.rowCount + numberOfTemps
+ property real tanksTempsHeight: root.height - (pumpButton.pumpEnabled ? pumpButton.height : 0)
+ property real tanksHeight: tankModel.rowCount > 0 ? tanksTempsHeight * tankModel.rowCount / tankTempCount : 0
+ property real tempsHeight: tanksTempsHeight - tanksHeight
+ property real minimumTankHeight: 21
+ property real maxTankHeight: 80
+ property real tankTileHeight: Math.min (Math.max (tanksTempsHeight / tankTempCount, minimumTankHeight), maxTankHeight)
+
+ property bool compact: tankTempCount > (pumpButton.pumpEnabled ? 5 : 6)
+
+ property string systemPrefix: "com.victronenergy.system"
+ VBusItem { id: vebusService; bind: Utils.path(systemPrefix, "/VebusService") }
+ property bool isMulti: vebusService.valid
+ property string veDirectInverterService: ""
+ property string inverterService: vebusService.valid ? vebusService.value : veDirectInverterService
+
+ property bool isInverter: ! isMulti && veDirectInverterService != ""
+ property bool hasAcInput: isMulti
+ VBusItem { id: _hasAcOutSystem; bind: "com.victronenergy.settings/Settings/SystemSetup/HasAcOutSystem" }
+ property bool hasAcOutSystem: _hasAcOutSystem.value === 1
+
+//////// add for system state
+ property bool hasSystemState: _systemState.valid
+
+//////// add for SYSTEM tile and voltage, power and frequency values
+ property VBusItem _systemState: VBusItem { bind: Utils.path(systemPrefix, "/SystemState/State") }
+//////// add for PV CHARGER voltage and current
+ property string pvChargerPrefix: ""
+ property int numberOfPvChargers: 0
+
+
+ //////// standard tile sizes
+ //////// positions are left, center, right and top, center, bottom of infoArea
+ property int tankWidth: 130
+
+ property int upperTileHeight: 185
+ property int acTileHeight: height - upperTileHeight
+
+ property int infoWidth: width - tankWidth
+ property int infoWidth3Column: infoWidth / 3
+ property int infoWidth2Column: infoWidth / 2
+
+//////// add for PV Charger voltage and current
+ VBusItem { id: pvNrTrackers; bind: Utils.path(pvChargerPrefix, "/NrOfTrackers") }
+ property bool singleTracker: ! pvNrTrackers.valid || pvNrTrackers.value == 1
+ property bool showPvVI: numberOfPvChargers == 1 && singleTracker
+ VBusItem { id: pvPower; bind: Utils.path(pvChargerPrefix, "/Yield/Power") }
+ VBusItem { id: pvVoltage; bind: Utils.path(pvChargerPrefix, singleTracker ? "/Pv/V" : "/Pv/0/V") }
+
+//////// add for inverter mode in STATUS
+ VBusItem { id: inverterMode; bind: Utils.path(inverterService, "/Mode") }
+
+//////// add for gauges
+ VBusItem { id: showGaugesItem; bind: Utils.path(guiModsPrefix, "/ShowGauges") }
+ property bool showGauges: showGaugesItem.valid ? showGaugesItem.value === 1 ? true : false : false
+
+//////// added to control time display
+ property string guiModsPrefix: "com.victronenergy.settings/Settings/GuiMods"
+ VBusItem { id: timeFormatItem; bind: Utils.path(guiModsPrefix, "/TimeFormat") }
+ property string timeFormat: getTimeFormat ()
+
+ function getTimeFormat ()
+ {
+ if (!timeFormatItem.valid || timeFormatItem.value === 0)
+ return ""
+ else if (timeFormatItem.value === 2)
+ return "h:mm ap"
+ else
+ return "hh:mm"
+ }
+
+ Component.onCompleted: { discoverServices(); showHelp () }
+
+ // define usable space for tiles but don't show anything
+ Rectangle {
+ id: infoArea
+ visible: false
+ anchors {
+ left: parent.left
+ right: tanksColum.left
+ top: parent.top;
+ bottom: parent.bottom;
+ }
+ }
+
+//////// change time to selectable 12/24 hour format
+ Timer {
+ id: wallClock
+ running: timeFormat != ""
+ repeat: true
+ interval: 1000
+ triggeredOnStart: true
+ onTriggered: time = Qt.formatDateTime(new Date(), timeFormat)
+ property string time
+ }
+
+ VBusItem { id: systemName; bind: Utils.path(settingsBindPreffix, "/Settings/SystemSetup/SystemName") }
+
+//////// copied SYSTEM from OverviewTiles.qml & combined SYSTEM and STATUS tiles
+ Tile {
+ title: qsTr("STATUS")
+ id: statusTile
+ anchors { left: parent.left; top: parent.top }
+ width: root.infoWidth3Column
+ height: root.upperTileHeight - inverterTile.height
+ color: "#4789d0"
+
+//////// relorder to give priority to errors
+ values: [
+ TileText {
+ text: systemName.valid && systemName.value !== "" ? systemName.value : sys.systemType.valid ? sys.systemType.value.toUpperCase() : ""
+ font.pixelSize: 16
+ wrapMode: Text.WordWrap
+ width: statusTile.width - 5
+ },
+ TileText {
+ text: wallClock.running ? wallClock.time : ""
+ font.pixelSize: 15
+ },
+//////// combine SystemReason with notifications
+ MarqueeEnhanced {
+ text:
+ {
+ if (activeNotifications.length === 0)
+ return systemReasonMessage.text
+ else
+ return notificationText() + " || " + systemReasonMessage.text
+ }
+ width: statusTile.width
+ textHorizontalAlignment: Text.AlignHCenter
+ interval: 100
+ SystemReasonMessage {
+ id: systemReasonMessage
+ }
+ },
+ TileText {
+ property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" }
+ property VeQuickItem speed: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Speed") }
+ property VeQuickItem speedUnit: VeQuickItem { uid: "dbus/com.victronenergy.settings/Settings/Gps/SpeedUnit" }
+
+ text: speed.value === undefined ? "" : getValue()
+ visible: speed.value !== undefined && speedUnit.value !== undefined
+
+ function getValue()
+ {
+ if (speed.value < 0.5) // blank speed if less than about 1 MPH
+ return " "
+ if (speedUnit.value === "km/h")
+ return (speed.value * 3.6).toFixed(1) + speedUnit.value
+ if (speedUnit.value === "mph")
+ return (speed.value * 2.236936).toFixed(1) + speedUnit.value
+ if (speedUnit.value === "kt")
+ return (speed.value * (3600/1852)).toFixed(1) + speedUnit.value
+ return speed.value.toFixed(2) + "m/s"
+ }
+ }
+ ]
+ } // end Tile STATUS
+ Tile
+ {
+ title: qsTr("INVERTER")
+ id: inverterTile
+ anchors { left: parent.left; top: statusTile.bottom }
+ width: root.infoWidth3Column
+ height: 62
+ color: "#4789d0"
+
+ values: [
+ TileText
+ {
+ text: inverterMode.valid ? inverterMode.text : "--"
+ },
+ TileText {
+ text: qsTr(systemState.text)
+
+ SystemState {
+ id: systemState
+ bind: hasSystemState?Utils.path(systemPrefix, "/SystemState/State"):Utils.path(inverterService, "/State")
+ }
+ }
+ ]
+////// add power bar graph
+ PowerGaugeMulti
+ {
+ id: multiBar
+ width: parent.width - 10
+ height: 8
+ anchors
+ {
+ top: parent.top; topMargin: 20
+ horizontalCenter: parent.horizontalCenter
+ }
+ inverterService: root.inverterService
+ visible: showGauges
+ }
+ DetailTarget { id: multiTarget; detailsPage: "DetailInverter.qml" }
+ } // end Tile INVERTER
+
+ Tile {
+ title: qsTr("BATTERY")
+ id: batteryTile
+ anchors { horizontalCenter: infoArea.horizontalCenter; top: infoArea.top }
+ width: root.infoWidth3Column
+ height: root.upperTileHeight
+
+ values: [
+ TileText // spacer
+ {
+ text: ""
+ font.pixelSize: 6
+ },
+ TileText {
+ text: sys.battery.soc.absFormat(0)
+ font.pixelSize: 22
+ //////// remove height (for consistency with other tiles)
+ },
+ TileText {
+ text: {
+ if (!sys.battery.state.valid)
+ return "---"
+ switch(sys.battery.state.value) {
+ //////// change - capitalized words look better
+ case sys.batteryStateIdle: return qsTr("Idle")
+ case sys.batteryStateCharging : return qsTr("Charging")
+ case sys.batteryStateDischarging : return qsTr("Discharging")
+ }
+ }
+ },
+ TileText {
+//////// change to show negative for battery drain
+ text: sys.battery.power.text
+ font.pixelSize: 18
+ },
+ TileText {
+ text: sys.battery.voltage.format(2)
+ },
+ TileText {
+ text: sys.battery.current.format(1)
+ },
+ TileText {
+ text: qsTr("Remaining:")
+ visible: timeToGo.valid
+ },
+ TileText {
+ id: timeToGoText
+ text: timeToGo.valid ? TTG.formatTimeToGo (timeToGo) : " "
+ visible: timeToGo.valid
+
+ VBusItem {
+ id: timeToGo
+ bind: Utils.path("com.victronenergy.system","/Dc/Battery/TimeToGo")
+ }
+ }
+ ]
+////// add battery current bar graph
+ PowerGaugeBattery
+ {
+ id: batteryBar
+ width: parent.width - 10
+ height: 8
+ endLabelFontSize: 14
+ endLabelBackgroundColor: batteryTile.color
+ anchors
+ {
+ top: parent.top; topMargin: 22
+ horizontalCenter: parent.horizontalCenter
+ }
+ visible: showGauges
+ }
+ DetailTarget { id: batteryTarget; detailsPage: "DetailBattery.qml" }
+ } // end Tile BATTERY
+
+ VBusItem { id: dcSystemNameItem; bind: Utils.path(settingsBindPreffix, "/Settings/GuiMods/CustomDcSystemName") }
+
+ Tile {
+ title: dcSystemNameItem.valid && dcSystemNameItem.value != "" ? dcSystemNameItem.value : qsTr ("DC SYSTEM")
+ id: dcSystem
+ anchors { right: infoArea.right; bottom: infoArea.bottom; bottomMargin: root.acTileHeight }
+ width: root.infoWidth3Column
+ height: (root.upperTileHeight / 2) - 5
+ color: "#16a085"
+ values: [
+ TileText { // spacer
+ font.pixelSize: 6
+ text: ""
+ },
+ TileText {
+ font.pixelSize: 22
+ text: EnhFmt.formatVBusItem (sys.dcSystem.power)
+ visible: sys.dcSystem.power.valid
+ },
+ ////// replace to/from battery with current
+ TileText {
+ text: !sys.dcSystem.power.valid ? "---" :
+ EnhFmt.formatValue (sys.dcSystem.power.value / sys.battery.voltage.value, "A")
+ visible: sys.dcSystem.power.valid
+ }
+ ]
+ PowerGauge
+ {
+ id: dcSystemGauge
+ width: parent.width - 10
+ height: 8
+ anchors
+ {
+ top: parent.top; topMargin: 22
+ horizontalCenter: parent.horizontalCenter
+ }
+ connection: sys.dcSystem
+ endLabelFontSize: 12
+ endLabelBackgroundColor: dcSystem.color
+ maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/DcSystemMaxLoad"
+ maxReversePowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/DcSystemMaxCharge"
+ showLabels: true
+ visible: showGauges && sys.dcSystem.power.valid
+ }
+ DetailTarget { id: dcSystemTarget; detailsPage: "DetailDcSystem.qml" }
+ } // end Tile DC SYSTEM
+
+ Tile {
+ id: solarTile
+ title: qsTr("PV CHARGER")
+ anchors { right: infoArea.right; top: infoArea.top }
+ width: root.infoWidth3Column
+ height: root.upperTileHeight - dcSystem.height
+ color: "#2cc36b"
+ values: [
+ TileText {
+ font.pixelSize: 22
+ text: EnhFmt.formatVBusItem (sys.pvCharger.power)
+ },
+ //////// add voltage
+ TileText {
+ text:
+ {
+ if (showPvVI)
+ return EnhFmt.formatVBusItem (pvVoltage, "V")
+ else
+ return ""
+ }
+ visible: showPvVI
+ },
+ //////// add current
+ TileText {
+ text:
+ {
+ if (showPvVI && pvPower.valid && pvVoltage.valid)
+ {
+ var voltage = pvVoltage.value
+ return EnhFmt.formatValue ((pvPower.value / voltage), "A")
+ }
+ else
+ return ""
+ }
+ visible: showPvVI
+ }
+ ]
+////// add power bar graph
+ PowerGauge
+ {
+ id: pvChargerBar
+ width: parent.width - 10
+ height: 8
+ anchors
+ {
+ top: parent.top; topMargin: 20
+ horizontalCenter: parent.horizontalCenter
+ }
+ connection: sys.pvCharger
+ maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/PvChargerMaxPower"
+ visible: showGauges && sys.pvCharger.power.valid
+ }
+ DetailTarget { id: pvChargerTarget; detailsPage: "DetailPvCharger.qml" }
+ } // end Tile PV CHARGER
+
+//////// add to display AC input ignored
+ VBusItem { id: ignoreAcInput; bind: Utils.path(inverterService, "/Ac/State/IgnoreAcIn1") }
+
+//////// add AC INPUT tile
+ Tile {
+ id: acInputTile
+ title: {
+ if (isInverter)
+ return qsTr ("No AC Input")
+ else if (ignoreAcInput.valid && ignoreAcInput.value == 1)
+ return qsTr ("AC In Ignored")
+ else
+ {
+ switch(sys.acSource) {
+ case 1: return qsTr("GRID")
+ case 2: return qsTr("GENERATOR")
+ case 3: return qsTr("SHORE POWER")
+ default: return qsTr("AC INPUT")
+ }
+ }
+ }
+ anchors { left: infoArea.left; bottom: infoArea.bottom }
+ width: root.infoWidth2Column
+ height: root.acTileHeight
+ color: "#82acde"
+//////// add voltage and current
+ VBusItem { id: currentLimit; bind: Utils.path(inverterService, "/Ac/ActiveIn/CurrentLimit") }
+ values: [
+ TileText {
+ visible: isMulti
+ text: EnhFmt.formatVBusItem (sys.acInput.power)
+ font.pixelSize: 20
+
+ },
+//////// add voltage and current
+ TileText {
+ visible: isMulti
+ text: EnhFmt.formatVBusItem (sys.acInput.voltageL1, "V") + " " + EnhFmt.formatVBusItem (sys.acInput.currentL1, "A") + " " + EnhFmt.formatVBusItem (sys.acInput.frequency, "Hz")
+ },
+ TileText
+ {
+ text: qsTr ("Limit: ") + EnhFmt.formatVBusItem (currentLimit, "A")
+ visible: currentLimit.valid
+ }
+ ]
+////// add power bar graph
+ PowerGauge
+ {
+ id: acInBar
+ width: parent.width - 10
+ height: 8
+ anchors
+ {
+ top: parent.top; topMargin: 20
+ horizontalCenter: parent.horizontalCenter
+ }
+ connection: sys.acInput
+ useInputCurrentLimit: true
+ maxForwardPowerParameter: ""
+ maxReversePowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/MaxFeedInPower"
+ visible: showGauges && hasAcInput
+ }
+ DetailTarget { id: acInputTarget; detailsPage: "DetailAcInput.qml" }
+ }
+
+ Tile {
+ title: qsTr("AC LOADS")
+ id: acLoadsTile
+ anchors { right: infoArea.right; bottom: infoArea.bottom}
+ width: root.infoWidth2Column
+ height: root.acTileHeight
+ color: "#e68e8a"
+//////// add voltage and current
+ VBusItem { id: outVoltage; bind: Utils.path(inverterService, "/Ac/Out/L1/V") }
+ VBusItem { id: outCurrent; bind: Utils.path(inverterService, "/Ac/Out/L1/I") }
+ VBusItem { id: outFrequency; bind: Utils.path(inverterService, "/Ac/Out/L1/F") }
+
+ values: [
+ TileText {
+ text: EnhFmt.formatVBusItem (sys.acLoad.power)
+ font.pixelSize: 22
+ },
+//////// add voltage and current - no frequency for VE.Direct inverter
+ TileText {
+ text:
+ {
+ var lineText = ""
+ if (isMulti || isInverter)
+ {
+ lineText = EnhFmt.formatVBusItem (outVoltage, "V") + " " + EnhFmt.formatVBusItem (outCurrent, "A")
+ if (isMulti)
+ lineText += " " + EnhFmt.formatVBusItem (outFrequency, "Hz")
+ }
+ return lineText
+ }
+ }
+ ]
+////// add power bar graph
+ PowerGauge
+ {
+ id: acLoadBar
+ width: parent.width - 10
+ height: 8
+ anchors
+ {
+ top: parent.top; topMargin: 20
+ horizontalCenter: parent.horizontalCenter
+ }
+ connection: sys.acLoad
+ maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/AcOutputMaxPower"
+ visible: showGauges && hasAcOutSystem
+ }
+ DetailTarget { id: acLoadsOnOutputTarget; detailsPage: "DetailLoadsOnOutput.qml" }
+ }
+
+ // Synchronise tank name text scroll start
+ Timer {
+ id: scrollTimer
+ interval: 15000
+ repeat: true
+ running: root.active
+ }
+
+ ListView {
+ id: tanksColum
+
+ anchors {
+ top: root.top
+ right: root.right
+ }
+ height: root.tanksHeight
+ width: root.tankWidth
+//////// make list flickable if more tiles than will fit completely
+ interactive: root.tankTileHeight * count > (tanksColum.height + 1) ? true : false
+
+ model: TankModel { id: tankModel }
+ delegate: TileTankEnhanced {
+ // Without an intermediate assignment this will trigger a binding loop warning.
+ property variant theService: DBusServices.get(buddy.id)
+ service: theService
+ width: tanksColum.width
+ height: root.tankTileHeight
+ pumpBindPrefix: root.pumpBindPreffix
+//////// modified to control compact differently
+ compact: root.compact
+ Connections {
+ target: scrollTimer
+ onTriggered: doScroll()
+ }
+ }
+ Tile {
+ title: qsTr("TANKS")
+ anchors.fill: parent
+ values: TileText {
+ text: qsTr("")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ }
+ z: -1
+ }
+ }
+
+//////// added temperature ListView and Model
+ ListView {
+ id: tempsColumn
+
+ anchors {
+ top: tanksColum.bottom
+ right: root.right
+ }
+ height: root.tempsHeight
+ width: root.tankWidth
+//////// make list flickable if more tiles than will fit completely
+ interactive: root.tankTileHeight * count > (tempsColumn.height + 1) ? true : false
+
+ model: tempsModel
+ delegate: TileTemp
+ {
+ width: tempsColumn.width
+ height: root.tankTileHeight
+//////// modified to control compact differently
+ compact: root.compact
+ Connections
+ {
+ target: scrollTimer
+ onTriggered: doScroll()
+ }
+ }
+ Tile
+ {
+ title: qsTr("TEMPS")
+ anchors.fill: parent
+ values: TileText
+ {
+ text: qsTr("")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ }
+ z: -1
+ }
+ }
+ ListModel { id: tempsModel }
+
+ Tile {
+ id: pumpButton
+
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ property variant texts: [ qsTr("AUTO"), qsTr("ON"), qsTr("OFF")]
+ property int value: 0
+ property bool reset: false
+ property bool pumpEnabled: pumpRelay.value === 3
+ isCurrentItem: false // not used by GuiMods key handler - focus shown a different way
+ //focus: root.active && isCurrentItem // don't switch focus -- messes up key handler
+
+ title: qsTr("PUMP")
+ width: pumpEnabled ? root.tankWidth : 0
+ height: 45
+ editable: true
+ readOnly: false
+ color: pumpButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8"
+
+ VBusItem { id: pump; bind: Utils.path(settingsBindPreffix, "/Settings/Pump0/Mode") }
+ VBusItem { id: pumpRelay; bind: Utils.path(settingsBindPreffix, "/Settings/Relay/Function") }
+
+ values: [
+ TileText {
+ text: pumpButton.pumpEnabled ? qsTr("%1").arg(pumpButton.texts[pumpButton.value]) : qsTr("DISABLED")
+ }
+ ]
+
+ function edit() {
+ if (!pumpEnabled) {
+ toast.createToast(qsTr("Pump functionality is not enabled. To enable it go to the relay settings page and set function to \"Tank pump\""), 5000)
+ return
+ }
+
+ reset = true
+ applyAnimation.restart()
+ reset = false
+
+ if (value < 2)
+ value++
+ else
+ value = 0
+ }
+
+ MouseArea {
+ id: pumpButtonMouseArea
+ property bool containsPressed: containsMouse && pressed
+ anchors.fill: parent
+ onClicked: {
+ parent.edit()
+ }
+ }
+
+ Rectangle {
+ id: timerRect
+ height: 2
+ width: pumpButton.width * 0.8
+ visible: applyAnimation.running
+ anchors {
+ bottom: parent.bottom; bottomMargin: 5
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ SequentialAnimation {
+ id: applyAnimation
+ alwaysRunToEnd: false
+ NumberAnimation {
+ target: timerRect
+ property: "width"
+ from: 0
+ to: pumpButton.width * 0.8
+ duration: 3000
+ }
+
+ ColorAnimation {
+ target: pumpButton
+ property: "color"
+ from: "#A8A8A8"
+ to: "#4789d0"
+ duration: 200
+ }
+
+ ColorAnimation {
+ target: pumpButton
+ property: "color"
+ from: "#4789d0"
+ to: "#A8A8A8"
+ duration: 200
+ }
+ PropertyAction {
+ target: timerRect
+ property: "width"
+ value: 0
+ }
+ // Do not set value if the animation is restarted by user pressing the button
+ // to move between options
+ onCompleted: if (!pumpButton.reset) pump.setValue(pumpButton.value)
+ }
+ DetailTarget { id: pumpButtonTarget; detailsPage: "" }
+ }
+
+ // When new service is found add resources as appropriate
+ Connections {
+ target: DBusServices
+ onDbusServiceFound: addService(service)
+ }
+
+ // hack to get value(s) from within a loop inside a function when service is changing
+ property string tempServiceName: ""
+ property VBusItem temperatureItem: VBusItem { bind: Utils.path(tempServiceName, "/Dc/0/Temperature") }
+
+//////// rewrite to use switch in place of if statements
+ function addService(service)
+ {
+ switch (service.type)
+ {
+//////// add for temp sensors
+ case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR:
+ numberOfTemps++
+ tempsModel.append({serviceName: service.name})
+ break;;
+ case DBusService.DBUS_SERVICE_MULTI:
+ root.tempServiceName = service.name
+ if (temperatureItem.valid)
+ {
+ numberOfTemps++
+ tempsModel.append({serviceName: service.name})
+ }
+ break;;
+//////// add for VE.Direct inverters
+ case DBusService.DBUS_SERVICE_INVERTER:
+ if (veDirectInverterService == "")
+ veDirectInverterService = service.name;
+ break;;
+
+//////// add for PV CHARGER voltage and current display
+ case DBusService.DBUS_SERVICE_SOLAR_CHARGER:
+ numberOfPvChargers++
+ if (pvChargerPrefix === "")
+ pvChargerPrefix = service.name;
+ break;;
+ case DBusService.DBUS_SERVICE_BATTERY:
+ root.tempServiceName = service.name
+ if (temperatureItem.valid)
+ {
+ numberOfTemps++
+ tempsModel.append({serviceName: service.name})
+ }
+ break;;
+ }
+ }
+
+ // Check available services to find tank sesnsors
+//////// rewrite to always call addService, removing redundant service type checks
+ function discoverServices()
+ {
+ numberOfTemps = 0
+ numberOfPvChargers = 0
+ veDirectInverterService = ""
+ tempsModel.clear()
+ for (var i = 0; i < DBusServices.count; i++)
+ addService(DBusServices.at(i))
+ }
+
+ function notificationText()
+ {
+ if (activeNotifications.length === 0)
+ return qsTr("")
+
+ var descr = []
+ for (var n = 0; n < activeNotifications.length; n++) {
+ var notification = activeNotifications[n];
+
+ var text = notification.serviceName + " - " + notification.description;
+ if (notification.value !== "" )
+ text += ": " + notification.value
+
+ descr.push(text)
+ }
+
+ return descr.join(" | ")
+ }
+
+ VBusItem { id: dmc; bind: Utils.path(inverterService, "/Devices/Dmc/Version") }
+ VBusItem { id: bms; bind: Utils.path(inverterService, "/Devices/Bms/Version") }
+
+
+
+// Details targets
+////// display detail targets and help message when first displayed.
+ Timer {
+ id: helpTimer
+ running: false
+ repeat: false
+ interval: 5000
+ triggeredOnStart: true
+ }
+
+ // help message shown when menu is first drawn
+ Rectangle
+ {
+ id: helpBox
+ color: "white"
+ width: 150
+ height: 32
+ opacity: 0.7
+ anchors
+ {
+ horizontalCenter: infoArea.horizontalCenter
+ verticalCenter: infoArea.verticalCenter
+ }
+ visible: false
+ }
+ TileText
+ {
+ text: qsTr ( "Tap tile center for detail at any time" )
+ color: "black"
+ anchors.fill: helpBox
+ wrapMode: Text.WordWrap
+ font.pixelSize: 12
+ visible: helpBox.visible
+ }
+
+
+ //// hard key handler
+ // used to press buttons when touch isn't available
+ // UP and DOWN buttons cycle through the list of touch areas
+ // "space" button is used to simulate a touch on the area
+ // target must be highlighted so that other uses of "space"
+ // will still occur
+
+ // list of all details touchable areas
+ // pump button sets value locally, no details page
+ // so is hanelded differently
+ // it must be LAST in the list because target list index is used for special processing
+ property variant targetList:
+ [
+ multiTarget, batteryTarget, pvChargerTarget, dcSystemTarget,
+ acInputTarget, acLoadsOnOutputTarget, pumpButtonTarget // pump MUST BE LAST
+ ]
+
+ property int selectedTarget: 0
+
+ Timer
+ {
+ id: targetTimer
+ interval: 5000
+ repeat: false
+ running: false
+ onTriggered: { hideAllTargets () }
+ }
+
+ Keys.forwardTo: [keyHandler]
+ Item
+ {
+ id: keyHandler
+ Keys.onUpPressed:
+ {
+ nextTarget (-1)
+ event.accepted = true
+ }
+
+ Keys.onDownPressed:
+ {
+ nextTarget (+1)
+ event.accepted = true
+ }
+ Keys.onSpacePressed:
+ {
+ if (targetTimer.running)
+ {
+ var foo // hack to make clicked() work
+ if (selectedTarget == targetList.length - 1)
+ pumpButton.edit ()
+ else
+ bar.clicked (foo)
+ event.accepted = true
+ }
+ else
+ event.accepted = false
+ }
+ }
+ // hack to make clicked() work
+ property variant bar: targetList[selectedTarget]
+
+ function nextTarget (increment)
+ {
+ // make one pass through all possible targets to find an enabled one
+ // if found, that's the new selectedTarget,
+ // if not selectedTarget does not change
+ var newIndex = selectedTarget
+ for (var i = 0; i < targetList.length; i++)
+ {
+ if (( ! targetTimer.running || helpBox.visible) && targetList[newIndex].enabled)
+ {
+ highlightSelectedTarget ()
+ return
+ }
+ newIndex += increment
+ if (newIndex >= targetList.length)
+ newIndex = 0
+ else if (newIndex < 0)
+ newIndex = targetList.length - 1
+ var includeTarget
+ if (newIndex == targetList.length - 1)
+ includeTarget = pumpButton.pumpEnabled
+ else
+ includeTarget = targetList[newIndex].enabled
+ if (includeTarget)
+ {
+ selectedTarget = newIndex
+ highlightSelectedTarget ()
+ break
+ }
+ }
+ }
+
+ function showHelp ()
+ {
+ for (var i = 0; i < targetList.length; i++)
+ {
+ targetList[i].targetVisible = true
+ }
+ helpBox.visible = true
+ targetTimer.restart ()
+ }
+ function hideAllTargets ()
+ {
+ for (var i = 0; i < targetList.length; i++)
+ targetList[i].targetVisible = false
+ helpBox.visible = false
+ }
+ function highlightSelectedTarget ()
+ {
+ for (var i = 0; i < targetList.length; i++)
+ {
+ if (targetList[i] == targetList[selectedTarget])
+ targetList[i].targetVisible = true
+ else
+ targetList[i].targetVisible = false
+ }
+ targetTimer.restart ()
+ helpBox.visible = false
+ }
+}
diff --git a/FileSets/v3.50~5/OverviewMobileEnhanced.qml.orig b/FileSets/v3.41/OverviewMobileEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~5/OverviewMobileEnhanced.qml.orig
rename to FileSets/v3.41/OverviewMobileEnhanced.qml.orig
diff --git a/FileSets/v3.41/OverviewTanksTempsDigInputs.qml b/FileSets/v3.41/OverviewTanksTempsDigInputs.qml
deleted file mode 120000
index 4fa05bdf..00000000
--- a/FileSets/v3.41/OverviewTanksTempsDigInputs.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~5/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/OverviewTanksTempsDigInputs.qml b/FileSets/v3.41/OverviewTanksTempsDigInputs.qml
new file mode 100644
index 00000000..781e163c
--- /dev/null
+++ b/FileSets/v3.41/OverviewTanksTempsDigInputs.qml
@@ -0,0 +1,207 @@
+//// New overview page for tanks, temps and digital inputs
+//// part of GuiMods
+//// based on tank/temps column in mobile overview
+
+import QtQuick 1.1
+import com.victron.velib 1.0
+import "utils.js" as Utils
+import "timeToGo.js" as TTG
+
+OverviewPage {
+ title: qsTr("Tanks & Temps & Digital Inputs")
+ id: root
+
+ property variant sys: theSystem
+ property string systemPrefix: "com.victronenergy.system"
+ property string settingsBindPreffix: "com.victronenergy.settings"
+ property string pumpBindPreffix: "com.victronenergy.pump.startstop0"
+
+ property int numberOfTanks: tankModel.rowCount
+ property real tanksHeight: root.height
+ property real minTankHeight: 21 // use for temps also
+ property real maxTankHeight: 80 // use for temps also
+ property real tankTileHeight: Math.min (Math.max (tanksHeight / numberOfTanks, minTankHeight), maxTankHeight)
+ property bool tanksCompact: numberOfTanks > 6
+
+ property int numberOfTemps: 0
+ property real tempsHeight: root.height
+ property real tempsTileHeight: Math.min (Math.max (tempsHeight / numberOfTemps, minTankHeight), maxTankHeight)
+ property bool tempsCompact: numberOfTemps > 6
+
+ property int tankWidth: parent.width / 3
+ property int tempsWidth: tankWidth
+ property int digInWidth: tankWidth
+
+ property int numberOfDigIn: 0
+ property real digInHeight: root.height
+ property real digInTileHeight: Math.min (Math.max (digInHeight / numberOfDigIn, minTankHeight), maxTankHeight)
+
+ Component.onCompleted: { discoverServices() }
+
+ // Synchronise name text scroll start
+ Timer {
+ id: scrollTimer
+ interval: 15000
+ repeat: true
+ running: root.active
+ }
+
+ ListView {
+ id: tanksColum
+
+ anchors {
+ top: root.top
+ left: root.left
+ }
+ height: root.tanksHeight
+ width: root.tankWidth
+ interactive: root.tankTileHeight * count > (tanksColum.height + 1) ? true : false
+
+ model: TankModel { id: tankModel }
+ delegate: TileTankEnhanced {
+ // Without an intermediate assignment this will trigger a binding loop warning.
+ property variant theService: DBusServices.get(buddy.id)
+ service: theService
+ width: tanksColum.width
+ height: root.tankTileHeight
+ pumpBindPrefix: root.pumpBindPreffix
+ compact: root.tanksCompact
+ Connections {
+ target: scrollTimer
+ onTriggered: doScroll()
+ }
+ }
+ Tile {
+ title: numberOfTanks == 0 ? qsTr ("no tanks") : qsTr("Tanks")
+ anchors.fill: parent
+ color: "#b3b3b3"
+ values: TileText {
+ text: qsTr("")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ }
+ z: -1
+ }
+ }
+
+ ListView {
+ id: tempsColumn
+
+ anchors {
+ top: root.top
+ left: tanksColum.right
+ }
+ height: root.tempsHeight
+ width: root.tempsWidth
+ // make list flickable if more tiles than will fit completely
+ interactive: root.tankTileHeight * count > (tempsColumn.height + 1) ? true : false
+
+ model: tempsModel
+ delegate: TileTemp
+ {
+ width: tempsColumn.width
+ height: root.tempsTileHeight
+ compact: root.tempsCompact
+ Connections
+ {
+ target: scrollTimer
+ onTriggered: doScroll()
+ }
+ }
+ Tile
+ {
+ title: numberOfTemps == 0 ? qsTr ("no temps") : qsTr("Temps")
+ anchors.fill: parent
+ color: "#b3b3b3"
+ values: TileText
+ {
+ text: qsTr("")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ }
+ z: -1
+ }
+ }
+ ListModel { id: tempsModel }
+
+ ListView {
+ id: digInputsColumn
+
+ anchors {
+ top: root.top
+ right: root.right
+ }
+ height: root.digInHeight
+ width: root.digInWidth
+ // make list flickable if more tiles than will fit completely
+ interactive: root.digInTileHeight * count > (digInputsColumn.height + 1) ? true : false
+
+ model: digInModel
+ delegate: TileDigIn
+ {
+ width: digInputsColumn.width
+ height: root.digInTileHeight
+ }
+ Tile
+ {
+ title: numberOfDigIn == 0 ? qsTr ("no digital inputs") : qsTr("Digital Inputs")
+ anchors.fill: parent
+ color: "#b3b3b3"
+ values: TileText
+ {
+ text: qsTr("")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ }
+ z: -1
+ }
+ }
+ ListModel { id: digInModel }
+
+
+ // When new service is found add resources as appropriate
+ Connections {
+ target: DBusServices
+ onDbusServiceFound: addService(service)
+ }
+
+ // hack to get value(s) from within a loop inside a function when service is changing
+ property string tempServiceName: ""
+ property VBusItem temperatureItem: VBusItem { bind: Utils.path(tempServiceName, "/Dc/0/Temperature") }
+
+ function addService(service)
+ {
+ switch (service.type)
+ {
+ case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR:
+ numberOfTemps++
+ tempsModel.append({serviceName: service.name})
+ break;;
+ case DBusService.DBUS_SERVICE_DIGITAL_INPUT:
+ case DBusService.DBUS_SERVICE_PULSE_COUNTER:
+ numberOfDigIn++
+ digInModel.append({serviceName: service.name})
+ break;;
+ case DBusService.DBUS_SERVICE_BATTERY:
+ case DBusService.DBUS_SERVICE_MULTI:
+ root.tempServiceName = service.name
+ if (temperatureItem.valid)
+ {
+ numberOfTemps++
+ tempsModel.append({serviceName: service.name})
+ }
+ break;;
+ }
+ }
+
+ // Check available services to find tank sesnsors
+ function discoverServices()
+ {
+ numberOfTemps = 0
+ tempsModel.clear()
+ numberOfDigIn = 0
+ digInModel.clear()
+ for (var i = 0; i < DBusServices.count; i++)
+ addService(DBusServices.at(i))
+ }
+}
diff --git a/FileSets/v3.50~5/OverviewTanksTempsDigInputs.qml.orig b/FileSets/v3.41/OverviewTanksTempsDigInputs.qml.orig
similarity index 100%
rename from FileSets/v3.50~5/OverviewTanksTempsDigInputs.qml.orig
rename to FileSets/v3.41/OverviewTanksTempsDigInputs.qml.orig
diff --git a/FileSets/v3.41/PageSettingsGenerator.qml b/FileSets/v3.41/PageSettingsGenerator.qml
deleted file mode 120000
index 5e12924e..00000000
--- a/FileSets/v3.41/PageSettingsGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/PageSettingsGenerator.qml b/FileSets/v3.41/PageSettingsGenerator.qml
new file mode 100644
index 00000000..2498ea6e
--- /dev/null
+++ b/FileSets/v3.41/PageSettingsGenerator.qml
@@ -0,0 +1,127 @@
+//// GuiMods
+//// added link to external state enable
+
+import QtQuick 1.1
+import com.victron.velib 1.0
+import "utils.js" as Utils
+
+MbPage {
+ id: root
+ title: qsTr("Generator start/stop settings")
+ property string settingsBindPrefix
+ property string startStopBindPrefix
+ property VBusItem acIn1Source: VBusItem { bind: "com.victronenergy.settings/Settings/SystemSetup/AcInput1" }
+ property VBusItem acIn2Source: VBusItem { bind: "com.victronenergy.settings/Settings/SystemSetup/AcInput2" }
+ property VBusItem capabilities: VBusItem { bind: Utils.path(startStopBindPrefix, "/Capabilities") }
+ property int warmupCapability: 1
+
+ model: VisibleItemModel {
+
+ MbSubMenu {
+ id: conditions
+ description: qsTr("Conditions")
+ subpage:
+ Component {
+ PageGeneratorConditions {
+ title: qsTr("Conditions")
+ bindPrefix: root.settingsBindPrefix
+ startStopBindPrefix: root.startStopBindPrefix
+ }
+ }
+ }
+
+ MbSpinBox {
+ description: qsTr("Minimum run time")
+ item {
+ bind: Utils.path(settingsBindPrefix, "/MinimumRuntime")
+ unit: "m"
+ decimals: 0
+ step: 1
+ }
+ }
+
+ MbSubMenu {
+ show: capabilities.value & warmupCapability
+ description: qsTr("Warm-up & cool-down")
+ subpage:
+ Component {
+ PageSettingsGeneratorWarmup {
+ title: qsTr("Warm-up & cool-down")
+ }
+ }
+ }
+
+ MbSwitch {
+ property bool generatorIsSet: acIn1Source.value === 2 || acIn2Source.value === 2
+ name: qsTr("Detect generator at AC input")
+ bind: Utils.path(settingsBindPrefix, "/Alarms/NoGeneratorAtAcIn")
+ enabled: valid && (generatorIsSet || checked)
+ onClicked: {
+ if (!checked) {
+ if (!generatorIsSet) {
+ toast.createToast(qsTr("None of the AC inputs is set to generator. Go to the system setup page and set the correct " +
+ "AC input to generator in order to enable this functionality."), 10000, "icon-info-active")
+ } else {
+ toast.createToast(qsTr("An alarm will be triggered when no power from the generator is detected at the inverter AC input. " +
+ "Make sure that the correct AC input is set to generator on the system setup page."), 12000, "icon-info-active")
+ }
+ }
+ }
+ }
+//// GuiMods
+ MbSwitch {
+ name: qsTr("Link to external running state")
+ bind: Utils.path(settingsBindPrefix, "/LinkToExternalStatus")
+ onClicked:
+ {
+ if (!checked)
+ toast.createToast(qsTr("Manual run will be synchronized with the generaror 'is running digital input' or AC input"), 10000, "icon-info-active")
+ }
+ }
+
+ MbSwitch {
+ name: qsTr("Alarm when generator is not in auto start mode")
+ bind: Utils.path(settingsBindPrefix, "/Alarms/AutoStartDisabled")
+ onClicked: {
+ if (!checked) {
+ toast.createToast(qsTr("An alarm will be triggered when auto start function is left disabled for more than 10 minutes."), 12000, "icon-info-active")
+ }
+ }
+ }
+
+ MbSwitch {
+ id: timeZones
+ name: qsTr("Quiet hours")
+ bind: Utils.path(settingsBindPrefix, "/QuietHours/Enabled")
+ enabled: valid
+ writeAccessLevel: User.AccessUser
+ }
+
+ MbEditBoxTime {
+ description: qsTr("Quiet hours start time")
+ item.bind: Utils.path(settingsBindPrefix, "/QuietHours/StartTime")
+ show: timeZones.checked
+ writeAccessLevel: User.AccessUser
+ }
+
+ MbEditBoxTime {
+ description: qsTr("Quiet hours end time")
+ item.bind: Utils.path(settingsBindPrefix, "/QuietHours/EndTime")
+ show: timeZones.checked
+ writeAccessLevel: User.AccessUser
+ }
+
+ MbSubMenu {
+ id: runtimePage
+ description: qsTr("Run time and service")
+ subpage:
+ Component {
+ PageGeneratorRuntimeService {
+ title: qsTr("Run time and service")
+ startStopBindPrefix: root.startStopBindPrefix
+ settingsBindPrefix: root.settingsBindPrefix
+ }
+ }
+ }
+ }
+}
diff --git a/FileSets/v3.50~22/PageSettingsGenerator.qml.orig b/FileSets/v3.41/PageSettingsGenerator.qml.orig
similarity index 100%
rename from FileSets/v3.50~22/PageSettingsGenerator.qml.orig
rename to FileSets/v3.41/PageSettingsGenerator.qml.orig
diff --git a/FileSets/v3.41/PageSettingsRelay.qml b/FileSets/v3.41/PageSettingsRelay.qml
deleted file mode 120000
index 41872500..00000000
--- a/FileSets/v3.41/PageSettingsRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/PageSettingsRelay.qml b/FileSets/v3.41/PageSettingsRelay.qml
new file mode 100644
index 00000000..60d9d900
--- /dev/null
+++ b/FileSets/v3.41/PageSettingsRelay.qml
@@ -0,0 +1,515 @@
+//////// modified to
+//////// add up to 18 relays
+//////// custom relay name for Relay Overview
+//////// show/hide relay in Relay Overview
+
+import QtQuick 1.1
+import com.victron.velib 1.0
+import "utils.js" as Utils
+
+MbPage {
+ id: pageRelaySettings
+ title: qsTr("Relay")
+ property string bindPrefix: "com.victronenergy.settings"
+ property VBusItem relay1Item: VBusItem { bind: "com.victronenergy.system/Relay/0/State" }
+ property bool hasRelay1: relay1Item.valid
+ property VBusItem relay2Item: VBusItem { bind: "com.victronenergy.system/Relay/1/State" }
+ property bool hasRelay2: relay2Item.valid
+ property VBusItem relay3Item: VBusItem { bind: "com.victronenergy.system/Relay/2/State" }
+ property bool hasRelay3: relay3Item.valid
+ property VBusItem relay4Item: VBusItem { bind: "com.victronenergy.system/Relay/3/State" }
+ property bool hasRelay4: relay4Item.valid
+ property VBusItem relay5Item: VBusItem { bind: "com.victronenergy.system/Relay/4/State" }
+ property bool hasRelay5: relay5Item.valid
+ property VBusItem relay6Item: VBusItem { bind: "com.victronenergy.system/Relay/5/State" }
+ property bool hasRelay6: relay6Item.valid
+ property VBusItem relay7Item: VBusItem { bind: "com.victronenergy.system/Relay/6/State" }
+ property bool hasRelay7: relay7Item.valid
+ property VBusItem relay8Item: VBusItem { bind: "com.victronenergy.system/Relay/7/State" }
+ property bool hasRelay8: relay8Item.valid
+ property VBusItem relay9Item: VBusItem { bind: "com.victronenergy.system/Relay/8/State" }
+ property bool hasRelay9: relay9Item.valid
+ property VBusItem relay10Item: VBusItem { bind: "com.victronenergy.system/Relay/9/State" }
+ property bool hasRelay10: relay10Item.valid
+ property VBusItem relay11Item: VBusItem { bind: "com.victronenergy.system/Relay/10/State" }
+ property bool hasRelay11: relay11Item.valid
+ property VBusItem relay12Item: VBusItem { bind: "com.victronenergy.system/Relay/11/State" }
+ property bool hasRelay12: relay12Item.valid
+ property VBusItem relay13Item: VBusItem { bind: "com.victronenergy.system/Relay/12/State" }
+ property bool hasRelay13: relay13Item.valid
+ property VBusItem relay14Item: VBusItem { bind: "com.victronenergy.system/Relay/13/State" }
+ property bool hasRelay14: relay14Item.valid
+ property VBusItem relay15Item: VBusItem { bind: "com.victronenergy.system/Relay/14/State" }
+ property bool hasRelay15: relay15Item.valid
+ property VBusItem relay16Item: VBusItem { bind: "com.victronenergy.system/Relay/15/State" }
+ property bool hasRelay16: relay16Item.valid
+ property VBusItem relay17Item: VBusItem { bind: "com.victronenergy.system/Relay/16/State" }
+ property bool hasRelay17: relay17Item.valid
+ property VBusItem relay18Item: VBusItem { bind: "com.victronenergy.system/Relay/17/State" }
+ property bool hasRelay18: relay18Item.valid
+
+ property VBusItem relay1NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/0/CustomName") }
+ property VBusItem relay2NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/1/CustomName") }
+ property VBusItem relay3NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/2/CustomName") }
+ property VBusItem relay4NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/3/CustomName") }
+ property VBusItem relay5NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/4/CustomName") }
+ property VBusItem relay6NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/5/CustomName") }
+ property VBusItem relay7NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/6/CustomName") }
+ property VBusItem relay8NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/7/CustomName") }
+ property VBusItem relay9NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/8/CustomName") }
+ property VBusItem relay10NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/9/CustomName") }
+ property VBusItem relay11NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/10/CustomName") }
+ property VBusItem relay12NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/11/CustomName") }
+ property VBusItem relay13NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/12/CustomName") }
+ property VBusItem relay14NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/13/CustomName") }
+ property VBusItem relay15NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/14/CustomName") }
+ property VBusItem relay16NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/15/CustomName") }
+ property VBusItem relay17NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/16/CustomName") }
+ property VBusItem relay18NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/17/CustomName") }
+
+ function relayName (nameItem, relayNumber)
+ {
+ var prefix, suffix
+ if (nameItem.valid && nameItem.value != "")
+ {
+ prefix = nameItem.value + " ("
+ suffix = ")"
+ }
+ else
+ {
+ prefix = ""
+ suffix = ""
+ }
+ if (relayNumber == 1)
+ return prefix + (hasRelay2 ? qsTr("Relay 1") : qsTr("Relay")) + suffix + " " + qsTr("On")
+ else
+ return prefix + qsTr("Relay") + " " + relayNumber + suffix + " " + qsTr("On")
+ }
+
+ model: VisibleItemModel {
+ MbItemOptions {
+ id: relay1Function
+ description: hasRelay2 ? qsTr("Function (Relay 1)") : qsTr("Function")
+ bind: Utils.path(bindPrefix, "/Settings/Relay/Function")
+ possibleValues:[
+ MbOption { description: qsTr("Alarm relay"); value: 0 },
+ MbOption { description: qsTr("Generator start/stop"); value: 1 },
+ MbOption { description: qsTr("Tank pump"); value: 3 },
+ MbOption { description: qsTr("Manual"); value: 2 },
+ MbOption { description: qsTr("Temperature"); value: 4 }
+ ]
+ show: hasRelay1
+ }
+
+ MbItemOptions {
+ description: qsTr("Alarm relay polarity")
+ bind: Utils.path(bindPrefix, "/Settings/Relay/Polarity")
+ show: hasRelay1 && relay1Function.value === 0
+ possibleValues: [
+ MbOption { description: qsTr("Normally open"); value: 0 },
+ MbOption { description: qsTr("Normally closed"); value: 1 }
+ ]
+ }
+
+ MbSwitch {
+ id: manualSwitch1
+ name: relayName (relay1NameItem, 1)
+ bind: "com.victronenergy.system/Relay/0/State"
+ show: hasRelay1 && relay1Function.value === 2 // manual mode
+ }
+
+ MbItemOptions {
+ id: relay2Function
+ description: hasRelay2 ? qsTr("Function (Relay 2)") : qsTr("Function")
+ bind: Utils.path(bindPrefix, "/Settings/Relay/1/Function")
+ show: hasRelay2
+ possibleValues:[
+ MbOption { description: qsTr("Manual"); value: 2 },
+ MbOption { description: qsTr("Temperature"); value: 4 }
+ ]
+ }
+ MbSwitch {
+ id: manualSwitch2
+ name: relayName (relay2NameItem, 2)
+ bind: "com.victronenergy.system/Relay/1/State"
+ show: hasRelay2 && relay2Function.value === 2
+ }
+ MbSwitch {
+ id: manualSwitch3
+ name: relayName (relay3NameItem, 3)
+ bind: "com.victronenergy.system/Relay/2/State"
+ show: hasRelay3
+ }
+ MbSwitch {
+ id: manualSwitch4
+ name: relayName (relay4NameItem, 4)
+ bind: "com.victronenergy.system/Relay/3/State"
+ show: hasRelay4
+ }
+ MbSwitch {
+ id: manualSwitch5
+ name: relayName (relay5NameItem, 5)
+ bind: "com.victronenergy.system/Relay/4/State"
+ show: hasRelay5
+ }
+ MbSwitch {
+ id: manualSwitch6
+ name: relayName (relay6NameItem, 6)
+ bind: "com.victronenergy.system/Relay/5/State"
+ show: hasRelay6
+ }
+ MbSwitch {
+ id: manualSwitch7
+ name: relayName (relay7NameItem, 7)
+ bind: "com.victronenergy.system/Relay/6/State"
+ show: hasRelay7
+ }
+ MbSwitch {
+ id: manualSwitch8
+ name: relayName (relay8NameItem, 8)
+ bind: "com.victronenergy.system/Relay/7/State"
+ show: hasRelay8
+ }
+ MbSwitch {
+ id: manualSwitch9
+ name: relayName (relay9NameItem, 9)
+ bind: "com.victronenergy.system/Relay/8/State"
+ show: hasRelay9
+ }
+ MbSwitch {
+ id: manualSwitch10
+ name: relayName (relay10NameItem, 10)
+ bind: "com.victronenergy.system/Relay/9/State"
+ show: hasRelay10
+ }
+ MbSwitch {
+ id: manualSwitch11
+ name: relayName (relay11NameItem, 11)
+ bind: "com.victronenergy.system/Relay/10/State"
+ show: hasRelay11
+ }
+ MbSwitch {
+ id: manualSwitch12
+ name: relayName (relay12NameItem, 12)
+ bind: "com.victronenergy.system/Relay/11/State"
+ show: hasRelay12
+ }
+ MbSwitch {
+ id: manualSwitch13
+ name: relayName (relay13NameItem, 13)
+ bind: "com.victronenergy.system/Relay/12/State"
+ show: hasRelay13
+ }
+ MbSwitch {
+ id: manualSwitch14
+ name: relayName (relay14NameItem, 14)
+ bind: "com.victronenergy.system/Relay/13/State"
+ show: hasRelay14
+ }
+ MbSwitch {
+ id: manualSwitch15
+ name: relayName (relay15NameItem, 15)
+ bind: "com.victronenergy.system/Relay/14/State"
+ show: hasRelay15
+ }
+ MbSwitch {
+ id: manualSwitch16
+ name: relayName (relay16NameItem, 16)
+ bind: "com.victronenergy.system/Relay/15/State"
+ show: hasRelay16
+ }
+ MbSwitch {
+ id: manualSwitch17
+ name: relayName (relay17NameItem, 17)
+ bind: "com.victronenergy.system/Relay/16/State"
+ show: hasRelay17
+ }
+ MbSwitch {
+ id: manualSwitch18
+ name: relayName (relay18NameItem, 18)
+ bind: "com.victronenergy.system/Relay/17/State"
+ show: hasRelay18
+ }
+
+ MbSubMenu {
+ id: conditions
+ description: qsTr("Temperature control rules")
+ show: relay1Function.value === 4 || relay2Function.value === 4
+ subpage: Component {
+ PageSettingsRelayTempSensors {
+ id: relayPage
+ title: qsTr("Temperature control rules")
+ }
+ }
+ }
+
+ MbEditBox {
+ id: relay1name
+ description: qsTr("Relay 1 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/0/CustomName"
+ show: hasRelay1 && item.valid && relay1Function.value === 2 // manual mode
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay1
+ name: qsTr("Show Relay 1 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/0/Show"
+ show: hasRelay1
+ }
+
+ MbEditBox {
+ id: relay2name
+ description: qsTr("Relay 2 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/1/CustomName"
+ show: hasRelay2 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay2
+ name: qsTr("Show Relay 2 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/1/Show"
+ show: hasRelay2
+ }
+
+ MbEditBox {
+ id: relay3name
+ description: qsTr("Relay 3 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/2/CustomName"
+ show: hasRelay3 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay3
+ name: qsTr("Show Relay 3 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/2/Show"
+ show: hasRelay3
+ }
+
+ MbEditBox {
+ id: relay4name
+ description: qsTr("Relay 4 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/3/CustomName"
+ show: hasRelay4 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay4
+ name: qsTr("Show Relay 4 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/3/Show"
+ show: hasRelay4
+ }
+
+ MbEditBox {
+ id: relay5name
+ description: qsTr("Relay 5 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/4/CustomName"
+ show: hasRelay5 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay5
+ name: qsTr("Show Relay 5 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/4/Show"
+ show: hasRelay5
+ }
+
+ MbEditBox {
+ id: relay6name
+ description: qsTr("Relay 6 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/5/CustomName"
+ show: hasRelay6 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay6
+ name: qsTr("Show Relay 6 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/5/Show"
+ show: hasRelay6
+ }
+
+ MbEditBox {
+ id: relay7name
+ description: qsTr("Relay 7 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/6/CustomName"
+ show: hasRelay7 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay7
+ name: qsTr("Show Relay 7 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/6/Show"
+ show: hasRelay7
+ }
+
+ MbEditBox {
+ id: relay8name
+ description: qsTr("Relay 8 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/7/CustomName"
+ show: hasRelay8 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay8
+ name: qsTr("Show Relay 8 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/7/Show"
+ show: hasRelay8
+ }
+
+ MbEditBox {
+ id: relay9name
+ description: qsTr("Relay 9 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/8/CustomName"
+ show: hasRelay9 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay9
+ name: qsTr("Show Relay 9 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/8/Show"
+ show: hasRelay9
+ }
+
+ MbEditBox {
+ id: relay10name
+ description: qsTr("Relay 10 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/9/CustomName"
+ show: hasRelay10 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay10
+ name: qsTr("Show Relay 10 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/9/Show"
+ show: hasRelay10
+ }
+
+ MbEditBox {
+ id: relay11name
+ description: qsTr("Relay 11 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/10/CustomName"
+ show: hasRelay11 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay11
+ name: qsTr("Show Relay 11 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/10/Show"
+ show: hasRelay11
+ }
+
+ MbEditBox {
+ id: relay12name
+ description: qsTr("Relay 12 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/11/CustomName"
+ show: hasRelay12 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay12
+ name: qsTr("Show Relay 12 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/11/Show"
+ show: hasRelay12
+ }
+
+ MbEditBox {
+ id: relay13name
+ description: qsTr("Relay 13 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/12/CustomName"
+ show: hasRelay13 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay13
+ name: qsTr("Show Relay 13 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/12/Show"
+ show: hasRelay13
+ }
+
+ MbEditBox {
+ id: relay14name
+ description: qsTr("Relay 14 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/13/CustomName"
+ show: hasRelay14 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay14
+ name: qsTr("Show Relay 14 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/13/Show"
+ show: hasRelay14
+ }
+
+ MbEditBox {
+ id: relay15name
+ description: qsTr("Relay 15 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/14/CustomName"
+ show: hasRelay15 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay15
+ name: qsTr("Show Relay 15 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/14/Show"
+ show: hasRelay15
+ }
+
+ MbEditBox {
+ id: relay16name
+ description: qsTr("Relay 16 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/15/CustomName"
+ show: hasRelay16 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay16
+ name: qsTr("Show Relay 16 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/15/Show"
+ show: hasRelay16
+ }
+
+ MbEditBox {
+ id: relay17name
+ description: qsTr("Relay 17 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/16/CustomName"
+ show: hasRelay17 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay17
+ name: qsTr("Show Relay 17 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/16/Show"
+ show: hasRelay17
+ }
+ MbEditBox {
+ id: relay18name
+ description: qsTr("Relay 18 Name")
+ item.bind: "com.victronenergy.settings/Settings/Relay/17/CustomName"
+ show: hasRelay18 && item.valid
+ maximumLength: 32
+ enableSpaceBar: true
+ }
+ MbSwitch {
+ id: showRelay18
+ name: qsTr("Show Relay 18 in overview")
+ bind: "com.victronenergy.settings/Settings/Relay/17/Show"
+ show: hasRelay18
+ }
+ }
+}
diff --git a/FileSets/v3.50~22/PageSettingsRelay.qml.orig b/FileSets/v3.41/PageSettingsRelay.qml.orig
similarity index 100%
rename from FileSets/v3.50~22/PageSettingsRelay.qml.orig
rename to FileSets/v3.41/PageSettingsRelay.qml.orig
diff --git a/FileSets/v3.41/PowerGauge.qml b/FileSets/v3.41/PowerGauge.qml
index 452ca646..8c1f5473 120000
--- a/FileSets/v3.41/PowerGauge.qml
+++ b/FileSets/v3.41/PowerGauge.qml
@@ -1 +1 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
+../v3.51~2/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/TileDigIn.qml b/FileSets/v3.41/TileDigIn.qml
deleted file mode 120000
index f11f1206..00000000
--- a/FileSets/v3.41/TileDigIn.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/TileDigIn.qml b/FileSets/v3.41/TileDigIn.qml
new file mode 100644
index 00000000..492b8b2d
--- /dev/null
+++ b/FileSets/v3.41/TileDigIn.qml
@@ -0,0 +1,133 @@
+// New for GuiMods to display digital inputs
+// based on TileTank.qml
+
+import QtQuick 1.1
+import "utils.js" as Utils
+import "tanksensor.js" as TankSensor
+
+Tile {
+ id: root
+
+ property string bindPrefix: serviceName
+ property VBusItem nameItem: VBusItem { bind: Utils.path(bindPrefix, "/CustomName") }
+ property VBusItem deviceItem: VBusItem { bind: Utils.path(bindPrefix, "/DeviceInstance") }
+ property VBusItem aggregateItem: VBusItem { bind: Utils.path(bindPrefix, "/Aggregate") }
+ property string digInName: nameItem.valid && nameItem.value != "" ? nameItem.value : getType (type)
+ property VBusItem typeItem: VBusItem { bind: Utils.path(bindPrefix, "/Type") }
+ property VBusItem stateItem: VBusItem { bind: Utils.path(bindPrefix, "/State") }
+ property bool isPulseCounter: aggregateItem.valid
+ // pulse counter doesn't have /Type so fill it in here
+ property int type: isPulseCounter ? 1 : typeItem.valid ? typeItem.value : 0
+
+ property variant bkgdColors: [ "#b3b3b3", "#4aa3df", "#1abc9c", "#F39C12", "#95a5a6", "#95a5a6","#dcc6e0", "#f1a9a0", "#7f8c8d", "#ebbc3a" ]
+ property color bkgdColor: type > 0 && type < 10 ? bkgdColors [type] : "#b3b3b3"
+ property variant units: ["m3", "L", "gal", "gal"]
+
+
+ function getType(type)
+ {
+ switch (type)
+ {
+ case 0:
+ return qsTr("Disabled")
+ case 1:
+ return qsTr("Pulse meter")
+ case 2:
+ return qsTr("Door alarm")
+ case 3:
+ return qsTr("Bilge pump")
+ case 4:
+ return qsTr("Bilge alarm")
+ case 5:
+ return qsTr("Burglar alarm")
+ case 6:
+ return qsTr("Smoke alarm")
+ case 7:
+ return qsTr("Fire alarm")
+ case 8:
+ return qsTr("CO2 alarm")
+ case 9:
+ return qsTr("Generator")
+ case 10:
+ return qsTr("Generic I/O")
+//// added for ExtTransferSwitch package
+ case 11:
+ return qsTr("Touch enable")
+ case 12:
+ return qsTr("Transfer switch")
+ default:
+ return "Unknown"
+ }
+ }
+
+ function getState(st)
+ {
+ switch (st)
+ {
+ case 0:
+ return qsTr("Low")
+ case 1:
+ return qsTr("High")
+ case 2:
+ return qsTr("Off")
+ case 3:
+ return qsTr("On")
+ case 4:
+ return qsTr("No")
+ case 5:
+ return qsTr("Yes")
+ case 6:
+ return qsTr("Open")
+ case 7:
+ return qsTr("Closed")
+ case 8:
+ return qsTr("Ok")
+ case 9:
+ return qsTr("Alarm")
+ case 10:
+ return qsTr("Running")
+ case 11:
+ return qsTr("Stopped")
+//// added for ExtTransferSwitch package
+ case 12:
+ return qsTr("On Generator")
+ case 13:
+ return qsTr("On Grid")
+ default:
+ return qsTr("Unknown")
+ }
+
+ }
+
+ title: digInName + " (In " + (deviceItem.valid ? (deviceItem.value.toString ()) : "?") + ")"
+
+ color: bkgdColor
+
+ VBusItem
+ {
+ id: unitItem
+ bind: Utils.path("com.victronenergy.settings/Settings/System/VolumeUnit")
+ }
+
+ values: Item
+ {
+ width: root.width - 10
+ height: 12
+ TileText
+ {
+ width: root.width
+ text:
+ {
+ if (isPulseCounter)
+ return aggregateItem.value.toString() + (unitItem.valid ? units[unitItem.value] : "??")
+ else
+ return stateItem.valid ? getState (stateItem.value) : "??"
+ }
+ horizontalAlignment: Text.AlignHCenter
+ anchors
+ {
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+ }
+}
diff --git a/FileSets/v3.50~22/TileDigIn.qml.orig b/FileSets/v3.41/TileDigIn.qml.orig
similarity index 100%
rename from FileSets/v3.50~22/TileDigIn.qml.orig
rename to FileSets/v3.41/TileDigIn.qml.orig
diff --git a/FileSets/v3.41/TileRelay.qml b/FileSets/v3.41/TileRelay.qml
index 5f86edbf..9cf7d223 120000
--- a/FileSets/v3.41/TileRelay.qml
+++ b/FileSets/v3.41/TileRelay.qml
@@ -1 +1 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.41/dbus_digitalinputs.py b/FileSets/v3.41/dbus_digitalinputs.py
deleted file mode 120000
index 717bc909..00000000
--- a/FileSets/v3.41/dbus_digitalinputs.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.41/dbus_digitalinputs.py b/FileSets/v3.41/dbus_digitalinputs.py
new file mode 100755
index 00000000..a49e9c6e
--- /dev/null
+++ b/FileSets/v3.41/dbus_digitalinputs.py
@@ -0,0 +1,651 @@
+#!/usr/bin/python3 -u
+
+#### modified for ExtTransferSwitch package
+
+import sys, os
+import signal
+from threading import Thread
+from select import select, epoll, EPOLLPRI
+from functools import partial
+from collections import namedtuple
+from argparse import ArgumentParser
+import traceback
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python'))
+
+from dbus.mainloop.glib import DBusGMainLoop
+import dbus
+from gi.repository import GLib
+from vedbus import VeDbusService, VeDbusItemImport
+from settingsdevice import SettingsDevice
+
+VERSION = '0.23'
+MAXCOUNT = 2**31-1
+SAVEINTERVAL = 60000
+
+INPUT_FUNCTION_COUNTER = 1
+INPUT_FUNCTION_INPUT = 2
+
+Translation = namedtuple('Translation', ['no', 'yes'])
+
+# Only append at the end
+INPUTTYPES = [
+ 'Disabled',
+ 'Pulse meter',
+ 'Door',
+ 'Bilge pump',
+ 'Bilge alarm',
+ 'Burglar alarm',
+ 'Smoke alarm',
+ 'Fire alarm',
+ 'CO2 alarm',
+ 'Generator',
+ 'Generic I/O',
+ 'Touch enable',
+#### added for ExtTransferSwitch package -- must be LAST in the list
+ 'Transfer switch'
+]
+
+# Translations. The text will be used only for GetText, it will be translated
+# in the gui.
+TRANSLATIONS = [
+ Translation('low', 'high'),
+ Translation('off', 'on'),
+ Translation('no', 'yes'),
+ Translation('open', 'closed'),
+ Translation('ok', 'alarm'),
+ Translation('running', 'stopped'),
+#### added for ExtTransferSwitch package
+ Translation('on generator', 'on grid')
+]
+
+class SystemBus(dbus.bus.BusConnection):
+ def __new__(cls):
+ return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM)
+
+class SessionBus(dbus.bus.BusConnection):
+ def __new__(cls):
+ return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
+
+class BasePulseCounter(object):
+ pass
+
+class DebugPulseCounter(BasePulseCounter):
+ def __init__(self):
+ self.gpiomap = {}
+
+ def register(self, path, gpio):
+ self.gpiomap[gpio] = None
+ return 0
+
+ def unregister(self, gpio):
+ del self.gpiomap[gpio]
+
+ def registered(self, gpio):
+ return gpio in self.gpiomap
+
+ def __call__(self):
+ from itertools import cycle
+ from time import sleep
+ for level in cycle([0, 1]):
+ for gpio in list(self.gpiomap.keys()):
+ yield gpio, level
+ sleep(0.25/len(self.gpiomap))
+
+class EpollPulseCounter(BasePulseCounter):
+ def __init__(self):
+ self.gpiomap = {}
+ self.states = {}
+ self.ob = epoll()
+
+ def register(self, path, gpio):
+ path = os.path.realpath(path)
+
+ # Set up gpio for rising edge interrupts
+ with open(os.path.join(path, 'edge'), 'ab') as fp:
+ fp.write(b'both')
+
+ fp = open(os.path.join(path, 'value'), 'rb')
+ level = int(fp.read()) # flush it in case it's high at startup
+ self.gpiomap[gpio] = fp
+ self.states[gpio] = level
+ self.ob.register(fp, EPOLLPRI)
+ return level
+
+ def unregister(self, gpio):
+ fp = self.gpiomap[gpio]
+ self.ob.unregister(fp)
+ del self.gpiomap[gpio]
+ del self.states[gpio]
+ fp.close()
+
+ def registered(self, gpio):
+ return gpio in self.gpiomap
+
+ def __call__(self):
+ while True:
+ # We have a timeout of 1 second on the poll, because poll() only
+ # looks at files in the epoll object at the time poll() was called.
+ # The timeout means we let other files (added via calls to
+ # register/unregister) into the loop at least that often.
+ self.ob.poll(1)
+
+ # When coming out of the epoll call, we read all the gpios to make
+ # sure we didn't miss any edges. This is a safety fallback that
+ # ensures everything is up to date once a second, but
+ # edge-triggered results are handled immediately.
+ # NOTE: There has not been a report of a missed interrupt yet.
+ # Belts and suspenders.
+ for gpio, fp in list(self.gpiomap.items()):
+ os.lseek(fp.fileno(), 0, os.SEEK_SET)
+ v = int(os.read(fp.fileno(), 1))
+ if v != self.states[gpio]:
+ self.states[gpio] = v
+ yield gpio, v
+
+class PollingPulseCounter(BasePulseCounter):
+ def __init__(self):
+ self.gpiomap = {}
+
+ def register(self, path, gpio):
+ path = os.path.realpath(path)
+
+ fp = open(os.path.join(path, 'value'), 'rb')
+ level = int(fp.read())
+ self.gpiomap[gpio] = [fp, level]
+ return level
+
+ def unregister(self, gpio):
+ del self.gpiomap[gpio]
+
+ def registered(self, gpio):
+ return gpio in self.gpiomap
+
+ def __call__(self):
+ from itertools import cycle
+ from time import sleep
+ while True:
+ for gpio, (fp, level) in list(self.gpiomap.items()):
+ fp.seek(0, os.SEEK_SET)
+ v = int(fp.read())
+ if v != level:
+ self.gpiomap[gpio][1] = v
+ yield gpio, v
+ sleep(1)
+
+class HandlerMaker(type):
+ """ Meta-class for keeping track of all extended classes. """
+ def __init__(cls, name, bases, attrs):
+ if not hasattr(cls, 'handlers'):
+ cls.handlers = {}
+ else:
+ cls.handlers[cls.type_id] = cls
+
+class PinHandler(object, metaclass=HandlerMaker):
+ product_id = 0xFFFF
+ _product_name = 'Generic GPIO'
+ dbus_name = "digital"
+ def __init__(self, bus, base, path, gpio, settings):
+ self.gpio = gpio
+ self.path = path
+ self.bus = bus
+ self.settings = settings
+ self._level = 0 # Remember last state
+
+ self.service = VeDbusService(
+ "{}.{}.input{:02d}".format(base, self.dbus_name, gpio), bus=bus)
+
+ # Add objects required by ve-api
+ self.service.add_path('/Mgmt/ProcessName', __file__)
+ self.service.add_path('/Mgmt/ProcessVersion', VERSION)
+ self.service.add_path('/Mgmt/Connection', path)
+ self.service.add_path('/DeviceInstance', gpio)
+ self.service.add_path('/ProductId', self.product_id)
+ self.service.add_path('/ProductName', self.product_name)
+ self.service.add_path('/Connected', 1)
+
+ # Custom name setting
+ def _change_name(p, v):
+ # This should fire a change event that will update product_name
+ # below.
+ settings['name'] = v
+ return True
+
+ self.service.add_path('/CustomName', settings['name'], writeable=True,
+ onchangecallback=_change_name)
+
+ # We'll count the pulses for all types of services
+ self.service.add_path('/Count', value=settings['count'])
+
+ @property
+ def product_name(self):
+ return self.settings['name'] or self._product_name
+
+ @product_name.setter
+ def product_name(self, v):
+ # Some pin types don't have an associated service (Disabled pins for
+ # example)
+ if self.service is not None:
+ self.service['/ProductName'] = v or self._product_name
+
+ def deactivate(self):
+ self.save_count()
+ self.service.__del__()
+ del self.service
+ self.service = None
+
+ @property
+ def level(self):
+ return self._level
+
+ @level.setter
+ def level(self, l):
+ self._level = int(bool(l))
+
+ def toggle(self, level):
+ raise NotImplementedError
+
+ def _toggle(self, level, service):
+ # Only increment Count on rising edge.
+ if level and level != self._level:
+ service['/Count'] = (service['/Count']+1) % MAXCOUNT
+ self._level = level
+
+ def refresh(self):
+ """ Toggle state to last remembered state. This is called if settings
+ are changed so the Service can recalculate paths. """
+ self.toggle(self._level)
+
+ def save_count(self):
+ if self.service is not None:
+ self.settings['count'] = self.count
+
+ @property
+ def active(self):
+ return self.service is not None
+
+ @property
+ def count(self):
+ return self.service['/Count']
+
+ @count.setter
+ def count(self, v):
+ self.service['/Count'] = v
+
+ @classmethod
+ def createHandler(cls, _type, *args, **kwargs):
+ if _type in cls.handlers:
+ return cls.handlers[_type](*args, **kwargs)
+ return None
+
+
+class NopPin(object):
+ """ Mixin for a pin with empty behaviour. Mix in BEFORE PinHandler so that
+ __init__ overrides the base behaviour. """
+ def __init__(self, bus, base, path, gpio, settings):
+ self.service = None
+ self.bus = bus
+ self.settings = settings
+ self._level = 0 # Remember last state
+
+ def deactivate(self):
+ pass
+
+ def toggle(self, level):
+ self._level = level
+
+ def save_count(self):
+ # Do nothing
+ pass
+
+ @property
+ def count(self):
+ return self.settings['count']
+
+ @count.setter
+ def count(self, v):
+ pass
+
+ def refresh(self):
+ pass
+
+
+class DisabledPin(NopPin, PinHandler):
+ """ Place holder for a disabled pin. """
+ _product_name = 'Disabled'
+ type_id = 0
+
+
+class VolumeCounter(PinHandler):
+ product_id = 0xA165
+ _product_name = "Generic pulse meter"
+ dbus_name = "pulsemeter"
+ type_id = 1
+
+ def __init__(self, bus, base, path, gpio, settings):
+ super(VolumeCounter, self).__init__(bus, base, path, gpio, settings)
+ self.service.add_path('/Aggregate', value=self.count*self.rate,
+ gettextcallback=lambda p, v: (str(v) + ' cubic meter'))
+
+ @property
+ def rate(self):
+ return self.settings['rate']
+
+ def toggle(self, level):
+ with self.service as s:
+ super(VolumeCounter, self)._toggle(level, s)
+ s['/Aggregate'] = self.count * self.rate
+
+class TouchEnable(NopPin, PinHandler):
+ """ The pin is used to enable/disable the Touch screen when toggled.
+ No dbus-service is created. """
+ _product_name = 'TouchEnable'
+ type_id = 11
+
+ def __init__(self, *args, **kwargs):
+ super(TouchEnable, self).__init__(*args, **kwargs)
+ self.item = VeDbusItemImport(self.bus,
+ "com.victronenergy.settings", "/Settings/Gui/TouchEnabled")
+
+ def toggle(self, level):
+ super(TouchEnable, self).toggle(level)
+
+ # Toggle the touch-enable setting on the downward edge.
+ # Level is expected to be high with the switch open, and
+ # pulled low when pushed.
+ if level == 0:
+ enabled = bool(self.item.get_value())
+ self.item.set_value(int(not enabled))
+
+ def deactivate(self):
+ # Always re-enable touch when the pin is deactivated.
+ # This adds another layer of protection against accidental
+ # lockout.
+ self.item.set_value(1)
+ del self.item
+
+class PinAlarm(PinHandler):
+ product_id = 0xA166
+ _product_name = "Generic digital input"
+ dbus_name = "digitalinput"
+ type_id = 0xFF
+ translation = 0 # low, high
+
+ def __init__(self, bus, base, path, gpio, settings):
+ super(PinAlarm, self).__init__(bus, base, path, gpio, settings)
+ self.service.add_path('/InputState', value=0)
+ self.service.add_path('/State', value=self.get_state(0),
+ gettextcallback=lambda p, v: TRANSLATIONS[v//2][v%2])
+ self.service.add_path('/Alarm', value=self.get_alarm_state(0))
+
+ # Also expose the type
+ self.service.add_path('/Type', value=self.type_id,
+ gettextcallback=lambda p, v: INPUTTYPES[v])
+
+ def toggle(self, level):
+ with self.service as s:
+ super(PinAlarm, self)._toggle(level, s)
+ s['/InputState'] = bool(level)*1
+ s['/State'] = self.get_state(level)
+ # Ensure that the alarm flag resets if the /AlarmSetting config option
+ # disappears.
+ s['/Alarm'] = self.get_alarm_state(level)
+
+ def get_state(self, level):
+ state = level ^ self.settings['invert']
+ return 2 * self.translation + state
+
+ def get_alarm_state(self, level):
+ return 2 * bool(
+ (level ^ self.settings['invertalarm']) and self.settings['alarm'])
+
+
+class Generator(PinAlarm):
+ _product_name = "Generator"
+ type_id = 9
+ translation = 5 # running, stopped
+
+ def __init__(self, *args, **kwargs):
+ super(Generator, self).__init__(*args, **kwargs)
+ # Periodically rewrite the generator selection. The Multi may reset
+ # causing this to be lost, or a race condition on startup may cause
+ # it to not be set properly.
+ self._timer = GLib.timeout_add(30000,
+ lambda: self.select_generator(self.level ^ self.settings['invert'] ^ 1) or True)
+
+#### added for ExtTransferSwitch package
+ self.mainVeBusServiceItem = None
+#### end added for ExtTransferSwitch package
+
+
+ def select_generator(self, v):
+
+ # Find all vebus services, and let them know
+ try:
+ services = [n for n in self.bus.list_names() if n.startswith(
+ 'com.victronenergy.vebus.')]
+ for n in services:
+#### added for ExtTransferSwitch package
+ # skip this service if it is the main VE.Bus device
+ # processing for that is handled in ExtTransferSwitch
+ try:
+ if self.mainVeBusServiceItem == None:
+ self.mainVeBusServiceItem = VeDbusItemImport(self.bus,
+ "com.victronenergy.service", "/VebusService")
+ if n == self.mainVeBusService.get_value ():
+ continue
+ except:
+ pass
+#### end added for ExtTransferSwitch package
+
+ self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', None,
+ 'SetValue', 'v', [v], None, None)
+ except dbus.exceptions.DBusException:
+ print ("DBus exception setting RemoteGeneratorSelected")
+ traceback.print_exc()
+
+ def toggle(self, level):
+ super(Generator, self).toggle(level)
+
+ # Follow the same inversion sense as for display
+ self.select_generator(level ^ self.settings['invert'] ^ 1)
+
+ def deactivate(self):
+ super(Generator, self).deactivate()
+ # When deactivating, reset the generator selection state
+ self.select_generator(0)
+
+ # And kill the periodic job
+ if self._timer is not None:
+ GLib.source_remove(self._timer)
+ self._timer = None
+
+# Various types of things we might want to monitor
+class DoorSensor(PinAlarm):
+ _product_name = "Door alarm"
+ type_id = 2
+ translation = 3 # open, closed
+
+class BilgePump(PinAlarm):
+ _product_name = "Bilge pump"
+ type_id = 3
+ translation = 1 # off, on
+
+class BilgeAlarm(PinAlarm):
+ _product_name = "Bilge alarm"
+ type_id = 4
+ translation = 4 # ok, alarm
+
+class BurglarAlarm(PinAlarm):
+ _product_name = "Burglar alarm"
+ type_id = 5
+ translation = 4 # ok, alarm
+
+class SmokeAlarm(PinAlarm):
+ _product_name = "Smoke alarm"
+ type_id = 6
+ translation = 4 # ok, alarm
+
+class FireAlarm(PinAlarm):
+ _product_name = "Fire alarm"
+ type_id = 7
+ translation = 4 # ok, alarm
+
+class CO2Alarm(PinAlarm):
+ _product_name = "CO2 alarm"
+ type_id = 8
+ translation = 4 # ok, alarm
+
+class GenericIO(PinAlarm):
+ _product_name = "Generic I/O"
+ type_id = 10
+ translation = 0 # low, high
+
+#### added for ExtTransferSwitch package
+class TransferSwitch(PinAlarm):
+ _product_name = "External AC Input transfer switch"
+ type_id = 12
+ translation = 6 # Grid In / Generator In
+
+
+def dbusconnection():
+ return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
+
+
+def main():
+ parser = ArgumentParser(description=sys.argv[0])
+ parser.add_argument('--servicebase',
+ help='Base service name on dbus, default is com.victronenergy',
+ default='com.victronenergy')
+ parser.add_argument('--poll',
+ help='Use a different kind of polling. Options are epoll, dumb and debug',
+ default='epoll')
+ parser.add_argument('inputs', nargs='+', help='Path to digital input')
+ args = parser.parse_args()
+
+ PulseCounter = {
+ 'debug': DebugPulseCounter,
+ 'poll': PollingPulseCounter,
+ }.get(args.poll, EpollPulseCounter)
+
+ DBusGMainLoop(set_as_default=True)
+
+ # Keep track of enabled services
+ services = {}
+ inputs = dict(enumerate(args.inputs, 1))
+ pulses = PulseCounter() # callable that iterates over pulses
+
+ def register_gpio(path, gpio, bus, settings):
+ _type = settings['inputtype']
+ print ("Registering GPIO {} for type {}".format(gpio, _type))
+
+ handler = PinHandler.createHandler(_type,
+ bus, args.servicebase, path, gpio, settings)
+ services[gpio] = handler
+
+ # Only monitor if enabled
+ if _type > 0:
+ handler.level = pulses.register(path, gpio)
+ handler.refresh()
+
+ def unregister_gpio(gpio):
+ print ("unRegistering GPIO {}".format(gpio))
+ pulses.unregister(gpio)
+ services[gpio].deactivate()
+
+ def handle_setting_change(inp, setting, old, new):
+ # This handler may also be called if some attribute of a setting
+ # is changed, but not the value. Bail if the value is unchanged.
+ if old == new:
+ return
+
+ if setting == 'inputtype':
+ if new:
+ # Get current bus and settings objects, to be reused
+ service = services[inp]
+ bus, settings = service.bus, service.settings
+
+ # Input enabled. If already enabled, unregister the old one first.
+ if pulses.registered(inp):
+ unregister_gpio(inp)
+
+ # Before registering the new input, reset its settings to defaults
+ settings['count'] = 0
+ settings['invert'] = 0
+ settings['invertalarm'] = 0
+ settings['alarm'] = 0
+
+ # Register it
+ register_gpio(inputs[inp], inp, bus, settings)
+ elif old:
+ # Input disabled
+ unregister_gpio(inp)
+ elif setting in ('rate', 'invert', 'alarm', 'invertalarm'):
+ services[inp].refresh()
+ elif setting == 'name':
+ services[inp].product_name = new
+ elif setting == 'count':
+ # Don't want this triggered on a period save, so only execute
+ # if it has changed.
+ v = int(new)
+ s = services[inp]
+ if s.count != v:
+ s.count = v
+ s.refresh()
+
+ for inp, pth in inputs.items():
+ supported_settings = {
+ 'inputtype': ['/Settings/DigitalInput/{}/Type'.format(inp), 0, 0, len(INPUTTYPES)-1],
+ 'rate': ['/Settings/DigitalInput/{}/Multiplier'.format(inp), 0.001, 0, 1.0],
+ 'count': ['/Settings/DigitalInput/{}/Count'.format(inp), 0, 0, MAXCOUNT, 1],
+ 'invert': ['/Settings/DigitalInput/{}/InvertTranslation'.format(inp), 0, 0, 1],
+ 'invertalarm': ['/Settings/DigitalInput/{}/InvertAlarm'.format(inp), 0, 0, 1],
+ 'alarm': ['/Settings/DigitalInput/{}/AlarmSetting'.format(inp), 0, 0, 1],
+ 'name': ['/Settings/DigitalInput/{}/CustomName'.format(inp), '', '', ''],
+ }
+ bus = dbusconnection()
+ sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, inp), timeout=10)
+ register_gpio(pth, inp, bus, sd)
+
+ def poll(mainloop):
+ from time import time
+ idx = 0
+
+ try:
+ for inp, level in pulses():
+ # epoll object only resyncs once a second. We may receive
+ # a pulse for something that's been deregistered.
+ try:
+ services[inp].toggle(level)
+ except KeyError:
+ continue
+ except:
+ traceback.print_exc()
+ mainloop.quit()
+
+ # Need to run the gpio polling in separate thread. Pass in the mainloop so
+ # the thread can kill us if there is an exception.
+ mainloop = GLib.MainLoop()
+
+ poller = Thread(target=lambda: poll(mainloop))
+ poller.daemon = True
+ poller.start()
+
+ # Periodically save the counter
+ def save_counters():
+ for inp in inputs:
+ services[inp].save_count()
+ return True
+ GLib.timeout_add(SAVEINTERVAL, save_counters)
+
+ # Save counter on shutdown
+ signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
+
+ try:
+ mainloop.run()
+ except KeyboardInterrupt:
+ pass
+ finally:
+ save_counters()
+
+if __name__ == "__main__":
+ main()
diff --git a/FileSets/v3.50~22/dbus_digitalinputs.py.orig b/FileSets/v3.41/dbus_digitalinputs.py.orig
similarity index 100%
rename from FileSets/v3.50~22/dbus_digitalinputs.py.orig
rename to FileSets/v3.41/dbus_digitalinputs.py.orig
diff --git a/FileSets/v3.41/startstop.py b/FileSets/v3.41/startstop.py
deleted file mode 120000
index 42f29aec..00000000
--- a/FileSets/v3.41/startstop.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.41/startstop.py b/FileSets/v3.41/startstop.py
new file mode 100644
index 00000000..ad4e1d9a
--- /dev/null
+++ b/FileSets/v3.41/startstop.py
@@ -0,0 +1,1567 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+#### GuiMods
+#### This file has been modified to allow the generator running state derived from the generator digital input
+#### or the genset AC input
+#### If the incoming generator state changes, the manual start state is updated
+#### time accumulation is suspended when the generator is not running
+#### A switch in the generator settings menucontrols whethter the incoming state affects manual start or time accumulaiton
+#### It is now possible to start the generator manually and have it stop automatically based on the preset conditions
+#### for automaitc start / stop
+#### A service interval timer was added so the accumulated run time does not need to be reset,
+#### providing total run time for the generator
+#### warm-up and cool-down periods have been modified in order to work well with an external transfer switch
+#### selecting grid or generator ahead of a MultiPlus input.
+#### Search for #### GuiMods to find changes
+
+# Function
+# dbus_generator monitors the dbus for batteries (com.victronenergy.battery.*) and
+# vebus com.victronenergy.vebus.*
+# Battery and vebus monitors can be configured through the gui.
+# It then monitors SOC, AC loads, battery current and battery voltage,to auto start/stop the generator based
+# on the configuration settings. Generator can be started manually or periodically setting a tes trun period.
+# Time zones function allows to use different values for the conditions along the day depending on time
+
+import dbus
+import datetime
+import calendar
+import time
+import sys
+import json
+import os
+import logging
+from collections import OrderedDict
+import monotonic_time
+from gen_utils import SettingsPrefix, Errors, States, enum
+from gen_utils import create_dbus_service
+# Victron packages
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python'))
+from ve_utils import exit_on_error
+from settingsdevice import SettingsDevice
+
+RunningConditions = enum(
+ Stopped = 0,
+ Manual = 1,
+ TestRun = 2,
+ LossOfCommunication = 3,
+ Soc = 4,
+ Acload = 5,
+ BatteryCurrent = 6,
+ BatteryVoltage = 7,
+ InverterHighTemp = 8,
+ InverterOverload = 9,
+ StopOnAc1 = 10,
+ StopOnAc2 = 11)
+
+Capabilities = enum(
+ WarmupCooldown = 1
+)
+
+SYSTEM_SERVICE = 'com.victronenergy.system'
+BATTERY_PREFIX = '/Dc/Battery'
+HISTORY_DAYS = 30
+AUTOSTART_DISABLED_ALARM_TIME = 600
+
+def safe_max(args):
+ try:
+ return max(x for x in args if x is not None)
+ except ValueError:
+ return None
+
+class Condition(object):
+ def __init__(self, parent):
+ self.parent = parent
+ self.reached = False
+ self.start_timer = 0
+ self.stop_timer = 0
+ self.valid = True
+ self.enabled = False
+ self.retries = 0
+
+ def __getitem__(self, key):
+ try:
+ return getattr(self, key)
+ except AttributeError:
+ raise KeyError(key)
+
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+ def get_value(self):
+ raise NotImplementedError("get_value")
+
+ @property
+ def vebus_service(self):
+ return self.parent._vebusservice if self.parent._vebusservice else ''
+
+ @property
+ def monitor(self):
+ return self.parent._dbusmonitor
+
+class SocCondition(Condition):
+ name = 'soc'
+ monitoring = 'battery'
+ boolean = False
+ timed = True
+
+ def get_value(self):
+ return self.parent._get_battery().soc
+
+class AcLoadCondition(Condition):
+ name = 'acload'
+ monitoring = 'vebus'
+ boolean = False
+ timed = True
+
+ def get_value(self):
+ loadOnAcOut = []
+ totalConsumption = []
+
+ for phase in ['L1', 'L2', 'L3']:
+ # Get the values directly from the inverter, systemcalc doesn't provide raw inverted power
+ loadOnAcOut.append(self.monitor.get_value(self.vebus_service, ('/Ac/Out/%s/P' % phase)))
+
+ # Calculate total consumption, '/Ac/Consumption/%s/Power' is deprecated
+ c_i = self.monitor.get_value(SYSTEM_SERVICE, ('/Ac/ConsumptionOnInput/%s/Power' % phase))
+ c_o = self.monitor.get_value(SYSTEM_SERVICE, ('/Ac/ConsumptionOnOutput/%s/Power' % phase))
+ totalConsumption.append(sum(filter(None, (c_i, c_o))))
+
+ # Invalidate if vebus is not available
+ if loadOnAcOut[0] == None:
+ return None
+
+ # Total consumption
+ if self.parent._settings['acloadmeasurement'] == 0:
+ return sum(filter(None, totalConsumption))
+
+ # Load on inverter AC out
+ if self.parent._settings['acloadmeasurement'] == 1:
+ return sum(filter(None, loadOnAcOut))
+
+ # Highest phase load
+ if self.parent._settings['acloadmeasurement'] == 2:
+ return safe_max(loadOnAcOut)
+
+class BatteryCurrentCondition(Condition):
+ name = 'batterycurrent'
+ monitoring = 'battery'
+ boolean = False
+ timed = True
+
+ def get_value(self):
+ c = self.parent._get_battery().current
+ if c is not None:
+ c *= -1
+ return c
+
+class BatteryVoltageCondition(Condition):
+ name = 'batteryvoltage'
+ monitoring = 'battery'
+ boolean = False
+ timed = True
+
+ def get_value(self):
+ return self.parent._get_battery().voltage
+
+class InverterTempCondition(Condition):
+ name = 'inverterhightemp'
+ monitoring = 'vebus'
+ boolean = True
+ timed = True
+
+ def get_value(self):
+ v = self.monitor.get_value(self.vebus_service,
+ '/Alarms/HighTemperature')
+
+ # When multi is connected to CAN-bus, alarms are published to
+ # /Alarms/HighTemperature... but when connected to vebus alarms are
+ # splitted in three phases and published to /Alarms/LX/HighTemperature...
+ if v is None:
+ inverterHighTemp = []
+ for phase in ['L1', 'L2', 'L3']:
+ # Inverter alarms must be fetched directly from the inverter service
+ inverterHighTemp.append(self.monitor.get_value(self.vebus_service, ('/Alarms/%s/HighTemperature' % phase)))
+ return safe_max(inverterHighTemp)
+ return v
+
+class InverterOverloadCondition(Condition):
+ name = 'inverteroverload'
+ monitoring = 'vebus'
+ boolean = True
+ timed = True
+
+ def get_value(self):
+ v = self.monitor.get_value(self.vebus_service,
+ '/Alarms/Overload')
+
+ # When multi is connected to CAN-bus, alarms are published to
+ # /Alarms/Overload... but when connected to vebus alarms are
+ # splitted in three phases and published to /Alarms/LX/Overload...
+ if v is None:
+ inverterOverload = []
+ for phase in ['L1', 'L2', 'L3']:
+ # Inverter alarms must be fetched directly from the inverter service
+ inverterOverload.append(self.monitor.get_value(self.vebus_service, ('/Alarms/%s/Overload' % phase)))
+ return safe_max(inverterOverload)
+ return v
+
+class StopOnAc1Condition(Condition):
+ name = 'stoponac1'
+ monitoring = 'vebus'
+ boolean = True
+ timed = False
+
+ def get_value(self):
+ # AC input 1
+ available = self.monitor.get_value(self.vebus_service,
+ '/Ac/State/AcIn1Available')
+ if available is None:
+ # Not supported in firmware, fall back to old behaviour
+ activein = self.monitor.get_value(self.vebus_service,
+ '/Ac/ActiveIn/ActiveInput')
+
+ # Active input is connected
+ connected = self.monitor.get_value(self.vebus_service,
+ '/Ac/ActiveIn/Connected')
+ if None not in (activein, connected):
+ return activein == 0 and connected == 1
+ return None
+
+ return bool(available)
+
+class StopOnAc2Condition(Condition):
+ name = 'stoponac2'
+ monitoring = 'vebus'
+ boolean = True
+ timed = False
+
+ def get_value(self):
+ # AC input 2 available (used when grid is on AC-in-2)
+ available = self.monitor.get_value(self.vebus_service,
+ '/Ac/State/AcIn2Available')
+
+ return None if available is None else bool(available)
+
+class Battery(object):
+ def __init__(self, monitor, service, prefix):
+ self.monitor = monitor
+ self.service = service
+ self.prefix = prefix
+
+ @property
+ def voltage(self):
+ return self.monitor.get_value(self.service, self.prefix + '/Voltage')
+
+ @property
+ def current(self):
+ return self.monitor.get_value(self.service, self.prefix + '/Current')
+
+ @property
+ def soc(self):
+ # Soc from the device doesn't have the '/Dc/0' prefix like the current and voltage do, but it does
+ # have the same prefix on systemcalc
+ return self.monitor.get_value(self.service, (BATTERY_PREFIX if self.prefix == BATTERY_PREFIX else '') + '/Soc')
+
+class StartStop(object):
+ _driver = None
+ def __init__(self, instance):
+#### GuiMods
+ logging.info ("GuiMods version of startstop.py")
+ self._currentTime = 0
+ self._last_update_mtime = 0
+ self._accumulatedRunTime = 0
+ self._digitalInputTypeObject = None
+ self._generatorInputStateObject = 0
+ self._lastState = 0
+ self._externalOverride = False
+ self._externalOverrideDelay = 99
+ self._lastExternalOverride = False
+ self._searchDelay = 99
+ self._linkToExternalState = False
+#### GuiMods warm-up / cool-down
+ self._warmUpEndTime = 0
+ self._coolDownEndTime = 0
+ self._ac1isIgnored = False
+ self._ac2isIgnored = False
+ self._activeAcInIsIgnored = False
+ self._acInIsGenerator = False
+
+ self._dbusservice = None
+ self._settings = None
+ self._dbusmonitor = None
+ self._remoteservice = None
+ self._name = None
+ self._enabled = False
+ self._instance = instance
+
+ # One second per retry
+ self.RETRIES_ON_ERROR = 300
+ self._testrun_soc_retries = 0
+ self._last_counters_check = 0
+
+ self._starttime = 0
+ self._stoptime = 0 # Used for cooldown
+ self._manualstarttimer = 0
+ self._last_runtime_update = 0
+ self._timer_runnning = 0
+
+ # The installer left autostart disabled
+ self._autostart_last_time = self._get_monotonic_seconds()
+ self._remote_start_mode_last_time = self._get_monotonic_seconds()
+
+
+ # Manual battery service selection is deprecated in favour
+ # of getting the values directly from systemcalc, we keep
+ # manual selected services handling for compatibility reasons.
+ self._vebusservice = None
+ self._errorstate = 0
+ self._battery_service = None
+ self._battery_prefix = None
+
+ self._acpower_inverter_input = {
+ 'timeout': 0,
+ 'unabletostart': False
+ }
+
+ # Order is important. Conditions are evaluated in the order listed.
+ self._condition_stack = OrderedDict({
+ SocCondition.name: SocCondition(self),
+ AcLoadCondition.name: AcLoadCondition(self),
+ BatteryCurrentCondition.name: BatteryCurrentCondition(self),
+ BatteryVoltageCondition.name: BatteryVoltageCondition(self),
+ InverterTempCondition.name: InverterTempCondition(self),
+ InverterOverloadCondition.name: InverterOverloadCondition(self),
+ StopOnAc1Condition.name: StopOnAc1Condition(self),
+ StopOnAc2Condition.name: StopOnAc2Condition(self)
+ })
+
+ def set_sources(self, dbusmonitor, settings, name, remoteservice):
+ self._settings = SettingsPrefix(settings, name)
+ self._dbusmonitor = dbusmonitor
+ self._remoteservice = remoteservice
+ self._name = name
+
+ self.log_info('Start/stop instance created for %s.' % self._remoteservice)
+ self._remote_setup()
+
+ def _create_service(self):
+ self._dbusservice = self._create_dbus_service()
+
+ # The driver used for this start/stop service
+ self._dbusservice.add_path('/Type', value=self._driver)
+ # State: None = invalid, 0 = stopped, 1 = running, 2=Warm-up, 3=Cool-down
+ self._dbusservice.add_path('/State', value=None, gettextcallback=lambda p, v: States.get_description(v))
+ # RunningByConditionCode: Numeric Companion to /RunningByCondition below, but
+ # also encompassing a Stopped state.
+ self._dbusservice.add_path('/RunningByConditionCode', value=None)
+ # Error
+ self._dbusservice.add_path('/Error', value=None, gettextcallback=lambda p, v: Errors.get_description(v))
+ # Condition that made the generator start
+ self._dbusservice.add_path('/RunningByCondition', value=None)
+ # Runtime
+ self._dbusservice.add_path('/Runtime', value=None, gettextcallback=self._seconds_to_text)
+ # Today runtime
+ self._dbusservice.add_path('/TodayRuntime', value=None, gettextcallback=self._seconds_to_text)
+ # Test run runtime
+ self._dbusservice.add_path('/TestRunIntervalRuntime', value=None , gettextcallback=self._seconds_to_text)
+ # Next test run date, values is 0 for test run disabled
+ self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=lambda p, v: datetime.datetime.fromtimestamp(v).strftime('%c'))
+ # Next test run is needed 1, not needed 0
+ self._dbusservice.add_path('/SkipTestRun', value=None)
+ # Manual start
+ self._dbusservice.add_path('/ManualStart', value=None, writeable=True)
+ # Manual start timer
+ self._dbusservice.add_path('/ManualStartTimer', value=None, writeable=True)
+ # Silent mode active
+ self._dbusservice.add_path('/QuietHours', value=None)
+ # Alarms
+ self._dbusservice.add_path('/Alarms/NoGeneratorAtAcIn', value=None)
+ self._dbusservice.add_path('/Alarms/ServiceIntervalExceeded', value=None)
+ self._dbusservice.add_path('/Alarms/AutoStartDisabled', value=None)
+ self._dbusservice.add_path('/Alarms/RemoteStartModeDisabled', value=None)
+ # Autostart
+ self._dbusservice.add_path('/AutoStartEnabled', value=None, writeable=True, onchangecallback=self._set_autostart)
+ # Accumulated runtime
+ self._dbusservice.add_path('/AccumulatedRuntime', value=None)
+ # Service interval
+ self._dbusservice.add_path('/ServiceInterval', value=None)
+ # Capabilities, where we can add bits
+ self._dbusservice.add_path('/Capabilities', value=0)
+ # Service countdown, calculated by running time and service interval
+ self._dbusservice.add_path('/ServiceCounter', value=None)
+ self._dbusservice.add_path('/ServiceCounterReset', value=None, writeable=True, onchangecallback=self._reset_service_counter)
+ # Publish what service we're controlling, and the productid
+ self._dbusservice.add_path('/GensetService', value=self._remoteservice)
+ self._dbusservice.add_path('/GensetInstance',
+ value=self._dbusmonitor.get_value(self._remoteservice, '/DeviceInstance'))
+ self._dbusservice.add_path('/GensetProductId',
+ value=self._dbusmonitor.get_value(self._remoteservice, '/ProductId'))
+
+ self._dbusservice.register()
+ # We need to set the values after creating the paths to trigger the 'onValueChanged' event for the gui
+ # otherwise the gui will report the paths as invalid if we remove and recreate the paths without
+ # restarting the dbusservice.
+ self._dbusservice['/State'] = 0
+ self._dbusservice['/RunningByConditionCode'] = RunningConditions.Stopped
+ self._dbusservice['/Error'] = 0
+ self._dbusservice['/RunningByCondition'] = ''
+ self._dbusservice['/Runtime'] = 0
+ self._dbusservice['/TodayRuntime'] = 0
+ self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(self._settings['testruninterval'])
+ self._dbusservice['/NextTestRun'] = None
+ self._dbusservice['/SkipTestRun'] = None
+ self._dbusservice['/ProductName'] = "Generator start/stop"
+ self._dbusservice['/ManualStart'] = 0
+ self._dbusservice['/ManualStartTimer'] = 0
+ self._dbusservice['/QuietHours'] = 0
+ self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 0
+ self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 0
+ self._dbusservice['/Alarms/AutoStartDisabled'] = 0 # GX auto start/stop
+ self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0 # Genset remote start mode
+ self._dbusservice['/AutoStartEnabled'] = self._settings['autostart']
+ self._dbusservice['/AccumulatedRuntime'] = int(self._settings['accumulatedtotal'])
+ self._dbusservice['/ServiceInterval'] = int(self._settings['serviceinterval'])
+ self._dbusservice['/ServiceCounter'] = None
+ self._dbusservice['/ServiceCounterReset'] = 0
+
+#### GuiMods
+ # generator input running state
+ self._dbusservice.add_path('/GeneratorRunningState', value=None)
+ # external override active
+ self._dbusservice.add_path('/ExternalOverride', value=None)
+ self._dbusservice['/GeneratorRunningState'] = "?"
+ self._dbusservice['/ExternalOverride'] = False
+ self._ignoreAutoStartCondition = False
+
+
+ @property
+ def capabilities(self):
+ return self._dbusservice['/Capabilities']
+
+ def _set_autostart(self, path, value):
+ if 0 <= value <= 1:
+ self._settings['autostart'] = int(value)
+ return True
+ return False
+
+ def enable(self):
+ if self._enabled:
+ return
+ self.log_info('Enabling auto start/stop and taking control of remote switch')
+ self._create_service()
+ self._determineservices()
+ self._update_remote_switch()
+ # If cooldown or warmup is enabled, the Quattro may be left in a bad
+ # state if there is an unfortunate crash or a reboot. Set the ignore_ac
+ # flag to a sane value on startup.
+ if self._settings['cooldowntime'] > 0 or \
+ self._settings['warmuptime'] > 0:
+ self._set_ignore_ac(False) ###########
+ self._enabled = True
+
+ def disable(self):
+ if not self._enabled:
+ return
+ self.log_info('Disabling auto start/stop, releasing control of remote switch')
+ self._remove_service()
+ self._enabled = False
+
+ def remove(self):
+ self.disable()
+ self.log_info('Removed from start/stop instances')
+
+ def _remove_service(self):
+ self._dbusservice.__del__()
+ self._dbusservice = None
+
+ def device_added(self, dbusservicename, instance):
+ self._determineservices()
+
+ def device_removed(self, dbusservicename, instance):
+ self._determineservices()
+
+ def get_error(self):
+ return self._dbusservice['/Error']
+
+ def set_error(self, errorn):
+ self._dbusservice['/Error'] = errorn
+
+ def clear_error(self):
+ self._dbusservice['/Error'] = Errors.NONE
+
+ def dbus_value_changed(self, dbusServiceName, dbusPath, options, changes, deviceInstance):
+ if self._dbusservice is None:
+ return
+
+ # AcIn1Available is needed to determine capabilities, but may
+ # only show up later. So we have to wait for it here.
+ if self._vebusservice is not None and \
+ dbusServiceName == self._vebusservice and \
+ dbusPath == '/Ac/State/AcIn1Available':
+ self._set_capabilities()
+
+ if dbusServiceName != 'com.victronenergy.system':
+ return
+ if dbusPath == '/AutoSelectedBatteryMeasurement' and self._settings['batterymeasurement'] == 'default':
+ self._determineservices()
+
+ if dbusPath == '/VebusService':
+ self._determineservices()
+
+ def handlechangedsetting(self, setting, oldvalue, newvalue):
+ if self._dbusservice is None:
+ return
+ if self._name not in setting:
+ # Not our setting
+ return
+
+ s = self._settings.removeprefix(setting)
+
+ if s == 'batterymeasurement':
+ self._determineservices()
+ # Reset retries and valid if service changes
+ for condition in self._condition_stack.values():
+ if condition['monitoring'] == 'battery':
+ condition['valid'] = True
+ condition['retries'] = 0
+
+ if s == 'autostart':
+ self.log_info('Autostart function %s.' % ('enabled' if newvalue == 1 else 'disabled'))
+ self._dbusservice['/AutoStartEnabled'] = self._settings['autostart']
+
+ if self._dbusservice is not None and s == 'testruninterval':
+ self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(
+ self._settings['testruninterval'])
+
+ if s == 'serviceinterval':
+ try:
+ self._dbusservice['/ServiceInterval'] = int(newvalue)
+ except TypeError:
+ pass
+ if newvalue == 0:
+ self._dbusservice['/ServiceCounter'] = None
+ else:
+ self._update_accumulated_time()
+ if s == 'lastservicereset':
+ self._update_accumulated_time()
+
+ def _reset_service_counter(self, path, value):
+ if (path == '/ServiceCounterReset' and value == int(1) and self._dbusservice['/AccumulatedRuntime']):
+ self._settings['lastservicereset'] = self._dbusservice['/AccumulatedRuntime']
+ self._update_accumulated_time()
+ self.log_info('Service counter reset triggered.')
+
+ return True
+
+ def _seconds_to_text(self, path, value):
+ m, s = divmod(value, 60)
+ h, m = divmod(m, 60)
+ return '%dh, %dm, %ds' % (h, m, s)
+
+ def log_info(self, msg):
+ logging.info(self._name + ': %s' % msg)
+
+ def tick(self):
+ if not self._enabled:
+ return
+
+#### GuiMods warm-up / cool-down
+ self._currentTime = self._get_monotonic_seconds ()
+
+ self._check_remote_status()
+#### GuiMods
+ self._linkToExternalState = self._settings['linkManualStartToExternal'] == 1
+ self._processGeneratorRunDetection ()
+
+ self._evaluate_startstop_conditions()
+ self._evaluate_autostart_disabled_alarm()
+ self._detect_generator_at_acinput()
+ if self._dbusservice['/ServiceCounterReset'] == 1:
+ self._dbusservice['/ServiceCounterReset'] = 0
+
+#### GuiMods warm-up / cool-down
+ state = self._dbusservice['/State']
+
+ # shed load for active generator input in warm-up and cool-down
+ # note that external transfer switch might change the state of on generator
+ # so this needs to be checked and load adjusted every pass
+ # restore load for sources no longer in use or if state is not in warm-up/cool-down
+ # restoring load is delayed 1following end of cool-down
+ # to allow the generator to actually stop producing power
+ if state in (States.WARMUP, States.COOLDOWN, States.STOPPING):
+ self._set_ignore_ac (True)
+ else:
+ self._set_ignore_ac (False)
+
+ # update cool down end time while running and generator has the load
+ # this is done because acInIsGenerator may change by an external transfer switch
+ # and we want an accurate picture of the cool down end time
+ # based on the last time the generatot was loaded
+ if state == States.RUNNING and self._acInIsGenerator:
+ self._coolDownEndTime = self._currentTime + self._settings['cooldowntime']
+#### end GuiMods warm-up / cool-down
+
+
+ def _evaluate_startstop_conditions(self):
+ if self.get_error() != Errors.NONE:
+ # First evaluation after an error, log it
+ if self._errorstate == 0:
+ self._errorstate = 1
+ self._dbusservice['/State'] = States.ERROR
+ self.log_info('Error: #%i - %s, stop controlling remote.' %
+ (self.get_error(),
+ Errors.get_description(self.get_error())))
+ elif self._errorstate == 1:
+ # Error cleared
+ self._errorstate = 0
+ self._dbusservice['/State'] = States.STOPPED
+ self.log_info('Error state cleared, taking control of remote switch.')
+
+ start = False
+ startbycondition = None
+ activecondition = self._dbusservice['/RunningByCondition']
+ today = calendar.timegm(datetime.date.today().timetuple())
+ self._timer_runnning = False
+ connection_lost = False
+ running = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP)
+
+ self._check_quiet_hours()
+
+ # New day, register it
+ if self._last_counters_check < today and self._dbusservice['/State'] == States.STOPPED:
+ self._last_counters_check = today
+ self._update_accumulated_time()
+
+ # Update current and accumulated runtime.
+#### GuiMods
+ self._accumulateRunTime ()
+
+#### GuiMods
+ # A negative /ManualStartTimer is used by the GUI to signal the generator should start now
+ # but stop when all auto stop conditions have been met
+ # so we skip manual start evaluation if this is the case
+ # and set a flag for use below to ignore auto start conditions
+ # the generator is actually started by the auto start/stop logic below
+ if self._dbusservice['/ManualStartTimer'] < 0 and self._dbusservice['/ManualStart'] == 1:
+ self._dbusservice['/ManualStartTimer'] = 0
+ self._dbusservice['/ManualStart'] = 0
+ self._ignoreAutoStartCondition = True
+
+ else:
+ self._ignoreAutoStartCondition = False
+ if self._evaluate_manual_start():
+ startbycondition = 'manual'
+ start = True
+
+ # Conditions will only be evaluated if the autostart functionality is enabled
+ if self._settings['autostart'] == 1:
+
+ if self._evaluate_testrun_condition():
+ startbycondition = 'testrun'
+ start = True
+
+ # Evaluate stop on AC IN conditions first, when this conditions are enabled and reached the generator
+ # will stop as soon as AC IN in active. Manual and testrun conditions will make the generator start
+ # or keep it running.
+ stop_on_ac_reached = (self._evaluate_condition(self._condition_stack[StopOnAc1Condition.name]) or
+ self._evaluate_condition(self._condition_stack[StopOnAc2Condition.name]))
+ stop_by_ac1_ac2 = startbycondition not in ['manual', 'testrun'] and stop_on_ac_reached
+
+ if stop_by_ac1_ac2 and running and activecondition not in ['manual', 'testrun']:
+ self.log_info('AC input available, stopping')
+
+ # Evaluate value conditions
+ for condition, data in self._condition_stack.items():
+ # Do not evaluate rest of conditions if generator is configured to stop
+ # when AC IN is available
+ if stop_by_ac1_ac2:
+ start = False
+ if running:
+ self._reset_condition(data)
+ continue
+ else:
+ break
+
+ # Don't short-circuit this, _evaluate_condition sets .reached
+ start = self._evaluate_condition(data) or start
+ startbycondition = condition if start and startbycondition is None else startbycondition
+ # Connection lost is set to true if the number of retries of one or more enabled conditions
+ # >= RETRIES_ON_ERROR
+ if data.enabled:
+ connection_lost = data.retries >= self.RETRIES_ON_ERROR
+
+ # If none condition is reached check if connection is lost and start/keep running the generator
+ # depending on '/OnLossCommunication' setting
+ if not start and connection_lost:
+ # Start always
+ if self._settings['onlosscommunication'] == 1:
+ start = True
+ startbycondition = 'lossofcommunication'
+ # Keep running if generator already started
+ if running and self._settings['onlosscommunication'] == 2:
+ start = True
+ startbycondition = 'lossofcommunication'
+
+#### GuiMods
+ ## auto start disabled and generator is stopped - clear the 'reached' flags
+ elif self._dbusservice['/State'] == States.STOPPED:
+ for condition, data in self._condition_stack.items():
+ self._reset_condition(data)
+
+ if not start and self._errorstate:
+ self._stop_generator()
+
+ if self._errorstate:
+ return
+
+ if start:
+ self._start_generator(startbycondition)
+#### GuiMods
+ # bypass the minimum run time check if External Override is active
+ elif (self._dbusservice['/Runtime'] >= self._settings['minimumruntime'] * 60
+ or activecondition == 'manual') or self._dbusservice['/ExternalOverride']:
+ self._stop_generator()
+
+ def _evaluate_autostart_disabled_alarm(self):
+
+ if self._settings['autostartdisabledalarm'] == 0:
+ self._autostart_last_time = self._get_monotonic_seconds()
+ self._remote_start_mode_last_time = self._get_monotonic_seconds()
+ if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
+ self._dbusservice['/Alarms/AutoStartDisabled'] = 0
+ if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
+ self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0
+ return
+
+ # GX auto start/stop alarm
+ if self._settings['autostart'] == 1:
+ self._autostart_last_time = self._get_monotonic_seconds()
+ if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
+ self._dbusservice['/Alarms/AutoStartDisabled'] = 0
+ else:
+ timedisabled = self._get_monotonic_seconds() - self._autostart_last_time
+ if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/AutoStartDisabled'] != 2:
+ self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
+ self._dbusservice['/Alarms/AutoStartDisabled'] = 2
+
+ # Genset remote start mode alarm
+ if self.get_error() != Errors.REMOTEDISABLED:
+ self._remote_start_mode_last_time = self._get_monotonic_seconds()
+ if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
+ self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0
+ else:
+ timedisabled = self._get_monotonic_seconds() - self._remote_start_mode_last_time
+ if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 2:
+ self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
+ self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 2
+
+
+#### GuiMods warm-up / cool-down - rewrote so acInIsGenerator is updated even if alarm is disabled
+ def _detect_generator_at_acinput(self):
+#### GuiMods warm-up / cool-down
+ self._acInIsGenerator = False # covers all conditions that result in a return
+
+ state = self._dbusservice['/State']
+ if state in [States.STOPPED, States.COOLDOWN, States.WARMUP]:
+ self._reset_acpower_inverter_input()
+ return
+
+ vebus_service = self._vebusservice if self._vebusservice else ''
+ activein_state = self._dbusmonitor.get_value(
+ vebus_service, '/Ac/ActiveIn/Connected')
+
+ # Path not supported, skip evaluation
+ if activein_state == None:
+ return
+
+ # Sources 0 = Not available, 1 = Grid, 2 = Generator, 3 = Shore
+ generator_acsource = self._dbusmonitor.get_value(
+ SYSTEM_SERVICE, '/Ac/ActiveIn/Source') == 2
+ # Not connected = 0, connected = 1
+ activein_connected = activein_state == 1
+
+#### GuiMods warm-up / cool-down
+ if self._settings['nogeneratoratacinalarm'] == 0:
+ processAlarm = False
+ self._reset_acpower_inverter_input()
+ else:
+ processAlarm = True
+
+ if generator_acsource and activein_connected:
+#### GuiMods warm-up / cool-down
+ self._acInIsGenerator = True
+#### GuiMods warm-up / cool-down
+ if processAlarm and self._acpower_inverter_input['unabletostart']:
+ self.log_info('Generator detected at inverter AC input, alarm removed')
+ self._reset_acpower_inverter_input()
+#### GuiMods warm-up / cool-down
+ elif not processAlarm:
+ self._reset_acpower_inverter_input()
+ return
+ elif self._acpower_inverter_input['timeout'] < self.RETRIES_ON_ERROR:
+ self._acpower_inverter_input['timeout'] += 1
+ elif not self._acpower_inverter_input['unabletostart']:
+ self._acpower_inverter_input['unabletostart'] = True
+ self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 2
+ self.log_info('Generator not detected at inverter AC input, triggering alarm')
+
+
+ def _reset_acpower_inverter_input(self, clear_error=True):
+ if self._acpower_inverter_input['timeout'] != 0:
+ self._acpower_inverter_input['timeout'] = 0
+
+ if self._acpower_inverter_input['unabletostart'] != 0:
+ self._acpower_inverter_input['unabletostart'] = 0
+
+ self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 0
+
+ def _reset_condition(self, condition):
+ condition['reached'] = False
+ if condition['timed']:
+ condition['start_timer'] = 0
+ condition['stop_timer'] = 0
+
+ def _check_condition(self, condition, value):
+ name = condition['name']
+
+ if self._settings[name + 'enabled'] == 0:
+ if condition['enabled']:
+ condition['enabled'] = False
+ self.log_info('Disabling (%s) condition' % name)
+ condition['retries'] = 0
+ condition['valid'] = True
+ self._reset_condition(condition)
+ return False
+
+ elif not condition['enabled']:
+ condition['enabled'] = True
+ self.log_info('Enabling (%s) condition' % name)
+
+ if (condition['monitoring'] == 'battery') and (self._settings['batterymeasurement'] == 'nobattery'):
+ # If no battery monitor is selected reset the condition
+ self._reset_condition(condition)
+ return False
+
+ if value is None and condition['valid']:
+ if condition['retries'] >= self.RETRIES_ON_ERROR:
+ logging.info('Error getting (%s) value, skipping evaluation till get a valid value' % name)
+ self._reset_condition(condition)
+ self._comunnication_lost = True
+ condition['valid'] = False
+ else:
+ condition['retries'] += 1
+ if condition['retries'] == 1 or (condition['retries'] % 10) == 0:
+ self.log_info('Error getting (%s) value, retrying(#%i)' % (name, condition['retries']))
+ return False
+
+ elif value is not None and not condition['valid']:
+ self.log_info('Success getting (%s) value, resuming evaluation' % name)
+ condition['valid'] = True
+ condition['retries'] = 0
+
+ # Reset retries if value is valid
+ if value is not None and condition['retries'] > 0:
+ self.log_info('Success getting (%s) value, resuming evaluation' % name)
+ condition['retries'] = 0
+
+ return condition['valid']
+
+ def _evaluate_condition(self, condition):
+ name = condition['name']
+ value = condition.get_value()
+ setting = ('qh_' if self._dbusservice['/QuietHours'] == 1 else '') + name
+ startvalue = self._settings[setting + 'start'] if not condition['boolean'] else 1
+ stopvalue = self._settings[setting + 'stop'] if not condition['boolean'] else 0
+
+ # Check if the condition has to be evaluated
+ if not self._check_condition(condition, value):
+ # If generator is started by this condition and value is invalid
+ # wait till RETRIES_ON_ERROR to skip the condition
+ if condition['reached'] and condition['retries'] <= self.RETRIES_ON_ERROR:
+ if condition['retries'] > 0:
+ return True
+
+ return False
+
+ # As this is a generic evaluation method, we need to know how to compare the values
+ # first check if start value should be greater than stop value and then compare
+ start_is_greater = startvalue > stopvalue
+
+#### GuiMods
+ stop = value <= stopvalue if start_is_greater else value >= stopvalue
+ # when starting manually and stopping based on auto stop values,
+ # start if stop condition is not satisfied
+
+ if self._ignoreAutoStartCondition:
+ start = not stop
+ else:
+ # When the condition is already reached only the stop value can set it to False
+ start = condition['reached'] or (value >= startvalue if start_is_greater else value <= startvalue)
+
+ # Timed conditions must start/stop after the condition has been reached for a minimum
+ # time.
+ if condition['timed']:
+ if not condition['reached'] and start:
+ condition['start_timer'] += time.time() if condition['start_timer'] == 0 else 0
+ start = time.time() - condition['start_timer'] >= self._settings[name + 'starttimer']
+ condition['stop_timer'] *= int(not start)
+ self._timer_runnning = True
+ else:
+ condition['start_timer'] = 0
+
+ if condition['reached'] and stop:
+ condition['stop_timer'] += time.time() if condition['stop_timer'] == 0 else 0
+ stop = time.time() - condition['stop_timer'] >= self._settings[name + 'stoptimer']
+ condition['stop_timer'] *= int(not stop)
+ self._timer_runnning = True
+ else:
+ condition['stop_timer'] = 0
+
+ condition['reached'] = start and not stop
+ return condition['reached']
+
+ def _evaluate_manual_start(self):
+ if self._dbusservice['/ManualStart'] == 0:
+ if self._dbusservice['/RunningByCondition'] == 'manual':
+ self._dbusservice['/ManualStartTimer'] = 0
+ return False
+
+ start = True
+ # If /ManualStartTimer has a value greater than zero will use it to set a stop timer.
+ # If no timer is set, the generator will not stop until the user stops it manually.
+ # Once started by manual start, each evaluation the timer is decreased
+#### GuiMods
+ if self._dbusservice['/ManualStartTimer'] > 0:
+ self._manualstarttimer += time.time() if self._manualstarttimer == 0 else 0
+ self._dbusservice['/ManualStartTimer'] -= int(time.time()) - int(self._manualstarttimer)
+ self._manualstarttimer = time.time()
+ start = self._dbusservice['/ManualStartTimer'] > 0
+ self._dbusservice['/ManualStart'] = int(start)
+ # Reset if timer is finished
+ self._manualstarttimer *= int(start)
+ self._dbusservice['/ManualStartTimer'] *= int(start)
+
+ return start
+
+ def _evaluate_testrun_condition(self):
+ if self._settings['testrunenabled'] == 0:
+ self._dbusservice['/SkipTestRun'] = None
+ self._dbusservice['/NextTestRun'] = None
+ return False
+
+ today = datetime.date.today()
+ yesterday = today - datetime.timedelta(days=1) # Should deal well with DST
+ now = time.time()
+ runtillbatteryfull = self._settings['testruntillbatteryfull'] == 1
+ soc = self._condition_stack['soc'].get_value()
+ batteryisfull = runtillbatteryfull and soc == 100
+ duration = 60 if runtillbatteryfull else self._settings['testrunruntime']
+
+ try:
+ startdate = datetime.date.fromtimestamp(self._settings['testrunstartdate'])
+ _starttime = time.mktime(yesterday.timetuple()) + self._settings['testrunstarttimer']
+
+ # today might in fact still be yesterday, if this test run started
+ # before midnight and finishes after. If `now` still falls in
+ # yesterday's window, then by the temporal anthropic principle,
+ # which I just made up but loosely states that time must have
+ # these properties for observers to exist, it must be yesterday
+ # because we are here to observe it.
+ if _starttime <= now <= _starttime + duration:
+ today = yesterday
+ starttime = _starttime
+ else:
+ starttime = time.mktime(today.timetuple()) + self._settings['testrunstarttimer']
+ except ValueError:
+ logging.debug('Invalid dates, skipping testrun')
+ return False
+
+ # If start date is in the future set as NextTestRun and stop evaluating
+ if startdate > today:
+ self._dbusservice['/NextTestRun'] = time.mktime(startdate.timetuple())
+ return False
+
+ start = False
+ # If the accumulated runtime during the tes trun interval is greater than '/TestRunIntervalRuntime'
+ # the tes trun must be skipped
+ needed = (self._settings['testrunskipruntime'] > self._dbusservice['/TestRunIntervalRuntime']
+ or self._settings['testrunskipruntime'] == 0)
+ self._dbusservice['/SkipTestRun'] = int(not needed)
+
+ interval = self._settings['testruninterval']
+ stoptime = starttime + duration
+ elapseddays = (today - startdate).days
+ mod = elapseddays % interval
+
+ start = not bool(mod) and starttime <= now <= stoptime
+
+ if runtillbatteryfull:
+ if soc is not None:
+ self._testrun_soc_retries = 0
+ start = (start or self._dbusservice['/RunningByCondition'] == 'testrun') and not batteryisfull
+ elif self._dbusservice['/RunningByCondition'] == 'testrun':
+ if self._testrun_soc_retries < self.RETRIES_ON_ERROR:
+ self._testrun_soc_retries += 1
+ start = True
+ if (self._testrun_soc_retries % 10) == 0:
+ self.log_info('Test run failed to get SOC value, retrying(#%i)' % self._testrun_soc_retries)
+ else:
+ self.log_info('Failed to get SOC after %i retries, terminating test run condition' % self._testrun_soc_retries)
+ start = False
+ else:
+ start = False
+
+ if not bool(mod) and (now <= stoptime):
+ self._dbusservice['/NextTestRun'] = starttime
+ else:
+ self._dbusservice['/NextTestRun'] = (time.mktime((today + datetime.timedelta(days=interval - mod)).timetuple()) +
+ self._settings['testrunstarttimer'])
+ return start and needed
+
+ def _check_quiet_hours(self):
+ active = False
+ if self._settings['quiethoursenabled'] == 1:
+ # Seconds after today 00:00
+ timeinseconds = time.time() - time.mktime(datetime.date.today().timetuple())
+ quiethoursstart = self._settings['quiethoursstarttime']
+ quiethoursend = self._settings['quiethoursendtime']
+
+ # Check if the current time is between the start time and end time
+ if quiethoursstart < quiethoursend:
+ active = quiethoursstart <= timeinseconds and timeinseconds < quiethoursend
+ else: # End time is lower than start time, example Start: 21:00, end: 08:00
+ active = not (quiethoursend < timeinseconds and timeinseconds < quiethoursstart)
+
+ if self._dbusservice['/QuietHours'] == 0 and active:
+ self.log_info('Entering to quiet mode')
+
+ elif self._dbusservice['/QuietHours'] == 1 and not active:
+ self.log_info('Leaving quiet mode')
+
+ self._dbusservice['/QuietHours'] = int(active)
+
+ return active
+
+ def _update_accumulated_time(self):
+ seconds = self._dbusservice['/Runtime']
+ accumulated = seconds - self._last_runtime_update
+
+ self._settings['accumulatedtotal'] = accumulatedtotal = int(self._settings['accumulatedtotal']) + accumulated
+ # Using calendar to get timestamp in UTC, not local time
+ today_date = str(calendar.timegm(datetime.date.today().timetuple()))
+
+ # If something goes wrong getting the json string create a new one
+ try:
+ accumulated_days = json.loads(self._settings['accumulateddaily'])
+ except ValueError:
+ accumulated_days = {today_date: 0}
+
+ if (today_date in accumulated_days):
+ accumulated_days[today_date] += accumulated
+ else:
+ accumulated_days[today_date] = accumulated
+
+ self._last_runtime_update = seconds
+
+ # Keep the historical with a maximum of HISTORY_DAYS
+ while len(accumulated_days) > HISTORY_DAYS:
+ accumulated_days.pop(min(accumulated_days.keys()), None)
+
+ # Upadate settings
+ self._settings['accumulateddaily'] = json.dumps(accumulated_days, sort_keys=True)
+ self._dbusservice['/TodayRuntime'] = self._interval_runtime(0)
+ self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(self._settings['testruninterval'])
+ self._dbusservice['/AccumulatedRuntime'] = accumulatedtotal
+
+ # Service counter
+ serviceinterval = self._settings['serviceinterval']
+ lastservicereset = self._settings['lastservicereset']
+ if serviceinterval > 0:
+ servicecountdown = (lastservicereset + serviceinterval) - accumulatedtotal
+ self._dbusservice['/ServiceCounter'] = servicecountdown
+ if servicecountdown <= 0:
+ self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 1
+ elif self._dbusservice['/Alarms/ServiceIntervalExceeded'] != 0:
+ self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 0
+
+
+
+ def _interval_runtime(self, days):
+ summ = 0
+ try:
+ daily_record = json.loads(self._settings['accumulateddaily'])
+ except ValueError:
+ return 0
+
+ for i in range(days + 1):
+ previous_day = calendar.timegm((datetime.date.today() - datetime.timedelta(days=i)).timetuple())
+ if str(previous_day) in daily_record.keys():
+ summ += daily_record[str(previous_day)] if str(previous_day) in daily_record.keys() else 0
+
+ return summ
+
+ def _get_battery(self):
+ if self._settings['batterymeasurement'] == 'default':
+ return Battery(self._dbusmonitor, SYSTEM_SERVICE, BATTERY_PREFIX)
+
+ return Battery(self._dbusmonitor,
+ self._battery_service if self._battery_service else '',
+ self._battery_prefix if self._battery_prefix else '')
+
+ def _set_capabilities(self):
+ # Update capabilities
+ # The ability to ignore AC1/AC2 came in at the same time as
+ # AC availability and is used to detect it here.
+ readout_supported = self._dbusmonitor.get_value(self._vebusservice,
+ '/Ac/State/AcIn1Available') is not None
+ self._dbusservice['/Capabilities'] |= (
+ Capabilities.WarmupCooldown if readout_supported else 0)
+
+ def _determineservices(self):
+ # batterymeasurement is either 'default' or 'com_victronenergy_battery_288/Dc/0'.
+ # In case it is set to default, we use the AutoSelected battery
+ # measurement, given by SystemCalc.
+ batterymeasurement = None
+ newbatteryservice = None
+ batteryprefix = ''
+ selectedbattery = self._settings['batterymeasurement']
+ vebusservice = None
+
+ if selectedbattery == 'default':
+ batterymeasurement = 'default'
+ elif len(selectedbattery.split('/', 1)) == 2: # Only very basic sanity checking..
+ batterymeasurement = self._settings['batterymeasurement']
+ elif selectedbattery == 'nobattery':
+ batterymeasurement = None
+ else:
+ # Exception: unexpected value for batterymeasurement
+ pass
+
+ if batterymeasurement and batterymeasurement != 'default':
+ batteryprefix = '/' + batterymeasurement.split('/', 1)[1]
+
+ # Get the current battery servicename
+ if self._battery_service:
+ oldservice = self._battery_service
+ else:
+ oldservice = None
+
+ if batterymeasurement != 'default':
+ battery_instance = int(batterymeasurement.split('_', 3)[3].split('/')[0])
+ service_type = None
+
+ if 'vebus' in batterymeasurement:
+ service_type = 'vebus'
+ elif 'battery' in batterymeasurement:
+ service_type = 'battery'
+
+ newbatteryservice = self._get_servicename_by_instance(battery_instance, service_type)
+ elif batterymeasurement == 'default':
+ newbatteryservice = 'default'
+
+ if newbatteryservice and newbatteryservice != oldservice:
+ if selectedbattery == 'default':
+ self.log_info('Getting battery values from systemcalc.')
+ if selectedbattery == 'nobattery':
+ self.log_info('Battery monitoring disabled! Stop evaluating related conditions')
+ self._battery_service = None
+ self._battery_prefix = None
+ self.log_info('Battery service we need (%s) found! Using it for generator start/stop' % batterymeasurement)
+ self._battery_service = newbatteryservice
+ self._battery_prefix = batteryprefix
+ elif not newbatteryservice and newbatteryservice != oldservice:
+ self.log_info('Error getting battery service!')
+ self._battery_service = newbatteryservice
+ self._battery_prefix = batteryprefix
+
+ # Get the default VE.Bus service
+ vebusservice = self._dbusmonitor.get_value('com.victronenergy.system', '/VebusService')
+ if vebusservice:
+ if self._vebusservice != vebusservice:
+ self._vebusservice = vebusservice
+ self._set_capabilities()
+ self.log_info('Vebus service (%s) found! Using it for generator start/stop' % vebusservice)
+ else:
+ if self._vebusservice is not None:
+ self.log_info('Vebus service (%s) dissapeared! Stop evaluating related conditions' % self._vebusservice)
+ else:
+ self.log_info('Error getting Vebus service!')
+ self._vebusservice = None
+
+ def _get_servicename_by_instance(self, instance, service_type=None):
+ sv = None
+ services = self._dbusmonitor.get_service_list()
+
+ for service in services:
+ if service_type and service_type not in service:
+ continue
+
+ if services[service] == instance:
+ sv = service
+ break
+
+ return sv
+
+ def _get_monotonic_seconds(self):
+ return monotonic_time.monotonic_time().to_seconds_double()
+
+ def _start_generator(self, condition):
+ state = self._dbusservice['/State']
+ remote_running = self._get_remote_switch_state()
+
+ # This function will start the generator in the case generator not
+ # already running. When differs, the RunningByCondition is updated
+ running = state in (States.WARMUP, States.COOLDOWN, States.STOPPING, States.RUNNING)
+ if not (running and remote_running): # STOPPED, ERROR
+#### GuiMods warm-up / cool-down
+ self.log_info('Starting generator by %s condition' % condition)
+ # if there is a warmup time specified, always go through warm-up state
+ # regardless of AC input in use
+ warmUpPeriod = self._settings['warmuptime']
+ if warmUpPeriod > 0:
+ self._warmUpEndTime = self._currentTime + warmUpPeriod
+ self.log_info ("starting warm-up")
+ self._dbusservice['/State'] = States.WARMUP
+ # no warm-up go directly to running
+ else:
+ self._dbusservice['/State'] = States.RUNNING
+ self._warmUpEndTime = 0
+
+ self._coolDownEndTime = 0
+ self._stoptime = 0
+
+ self._update_remote_switch()
+ else: # WARMUP, COOLDOWN, RUNNING, STOPPING
+ if state in (States.COOLDOWN, States.STOPPING):
+ # Start request during cool-down run, go back to RUNNING
+ self.log_info ("aborting cool-down - returning to running")
+ self._dbusservice['/State'] = States.RUNNING
+
+ elif state == States.WARMUP:
+ if self._currentTime > self._warmUpEndTime:
+ self.log_info ("warm-up complete")
+ self._dbusservice['/State'] = States.RUNNING
+
+ # Update the RunningByCondition
+ if self._dbusservice['/RunningByCondition'] != condition:
+ self.log_info('Generator previously running by %s condition is now running by %s condition'
+ % (self._dbusservice['/RunningByCondition'], condition))
+#### end GuiMods warm-up / cool-down
+
+
+ self._dbusservice['/RunningByCondition'] = condition
+ self._dbusservice['/RunningByConditionCode'] = RunningConditions.lookup(condition)
+
+
+ def _stop_generator(self):
+ state = self._dbusservice['/State']
+ remote_running = self._get_remote_switch_state()
+ running = state in (States.WARMUP, States.COOLDOWN, States.STOPPING, States.RUNNING)
+
+ if running or remote_running:
+#### GuiMods warm-up / cool-down
+ if state == States.RUNNING:
+ state = States.COOLDOWN
+ if self._currentTime < self._coolDownEndTime:
+ self.log_info ("starting cool-down")
+ elif self._settings['cooldowntime'] != 0:
+ self.log_info ("skipping cool-down -- no AC load on generator")
+
+ # warm-up should also transition to stopping
+ # cool-down time will have expired since it's set to 0 when starting
+ # and there has not yet been a load on the generator
+ if state in (States.WARMUP, States.COOLDOWN):
+ # cool down complete
+ if self._currentTime > self._coolDownEndTime:
+ state = States.STOPPING
+ self.log_info('Stopping generator that was running by %s condition' %
+ str(self._dbusservice['/RunningByCondition']))
+ self._update_remote_switch() # Stop engine
+ self._stoptime = self._currentTime + self._settings['generatorstoptime']
+ if self._currentTime < self._stoptime:
+ self.log_info ("waiting for generator so stop")
+
+ if state == States.STOPPING:
+ # wait for stop period expired - finish up transition to STOPPED
+ if self._currentTime > self._stoptime:
+ if self._settings['generatorstoptime'] != 0:
+ self.log_info ("generator stop time reached - OK to reconnect AC")
+ state = States.STOPPED
+ self._update_remote_switch()
+ self._dbusservice['/RunningByCondition'] = ''
+ self._dbusservice['/RunningByConditionCode'] = RunningConditions.Stopped
+ self._update_accumulated_time()
+ self._starttime = 0
+ self._dbusservice['/Runtime'] = 0
+ self._dbusservice['/ManualStartTimer'] = 0
+ self._manualstarttimer = 0
+ self._last_runtime_update = 0
+
+ self._dbusservice['/State'] = state
+#### end GuiMods warm-up / cool-down
+
+
+ @property
+ def _ac1_is_generator(self):
+ return self._dbusmonitor.get_value('com.victronenergy.settings',
+ '/Settings/SystemSetup/AcInput1') == 2
+
+ @property
+ def _ac2_is_generator(self):
+ return self._dbusmonitor.get_value('com.victronenergy.settings',
+ '/Settings/SystemSetup/AcInput2') == 2
+
+ def _set_ignore_ac(self, ignore):
+ # This is here so the Multi/Quattro can be told to disconnect AC-in,
+ # so that we can do warm-up and cool-down.
+#### GuiMods warm-up / cool-down
+ # stock code does not handle changes in the input type
+ # which could happen with an external transfer switch
+ # doing things this way should handle it
+
+ self._activeAcInIsIgnored = ignore
+ ignore1 = False
+ ignore2 = False
+ if self._ac1_is_generator:
+ ignore1 = ignore
+ elif self._ac2_is_generator:
+ ignore2 = ignore
+
+ if ignore1 != self._ac1isIgnored:
+ if ignore1:
+ self.log_info ("shedding load - AC input 1")
+ else:
+ self.log_info ("restoring load - AC input 1")
+ self._dbusmonitor.set_value_async(self._vebusservice, '/Ac/Control/IgnoreAcIn1', dbus.Int32(ignore1, variant_level=1))
+ self._ac1isIgnored = ignore1
+
+ if ignore2 != self._ac2isIgnored:
+ if ignore2:
+ self.log_info ("shedding load - AC input 2")
+ else:
+ self.log_info ("restoring load - AC input 2")
+ self._dbusmonitor.set_value_async(self._vebusservice, '/Ac/Control/IgnoreAcIn2', dbus.Int32(ignore2, variant_level=1))
+ self._ac2isIgnored = ignore2
+#### end GuiMods warm-up / cool-down
+
+
+ def _update_remote_switch(self):
+ # Engine should be started in these states
+ v = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN)
+ self._set_remote_switch_state(dbus.Int32(v, variant_level=1))
+#### GuiMods
+ if v == True:
+ self.log_info ("updating remote switch to running")
+ else:
+ self.log_info ("updating remote switch to stopped")
+
+ def _get_remote_switch_state(self):
+ raise Exception('This function should be overridden')
+
+ def _set_remote_switch_state(self, value):
+ raise Exception('This function should be overridden')
+
+ # Check the remote status, for example errors
+ def _check_remote_status(self):
+ raise Exception('This function should be overridden')
+
+ def _remote_setup(self):
+ raise Exception('This function should be overridden')
+
+ 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):
+ return create_dbus_service(self._instance)
+
+
+#### GuiMods
+
+# this function connects the generator digital input (if any)
+# OR the generator AC input detection
+# to the generator /ManualStart and updates dbus paths used by the GUI
+#
+# if the generator digital input changes from stopped to running
+# AND no run conditions are active, a manual start is innitiated
+#
+# if the generator digital input changes from running to stopped
+# AND a manual start is active, a manual stop is innitiated
+#
+# /GeneratorRunningState provides the input running state from the digital input to the GUI
+# R = running
+# S = stopped
+# ? = unknown (no digital input found)
+#
+# /ExternalOverride is used by the GUI to alert the user when there is a conflict
+# between the generator running state and the state Venus
+# /ExternalOverride is True if /GeneratorRunningState is S
+# AND the /RunningCondition is not stopped (which includes a manual run)
+# activation is delayed 5 seconds to allow transitions to settle
+#
+# we must first find the geneator digital input, if it exists at all
+# we serche all dBus services looking for a digital input with type generator (9)
+# the search only occurs every 10 seconds
+#
+
+ def _processGeneratorRunDetection (self):
+ TheBus = dbus.SystemBus()
+ generatorState = self._dbusservice['/State']
+ try:
+ # current input service is no longer valid
+ # search for a new one only every 10 seconds to avoid unnecessary processing
+ if (self._digitalInputTypeObject == None or self._digitalInputTypeObject.GetValue() != 9) and self._searchDelay > 10:
+ newInputService = ""
+ for service in TheBus.list_names():
+ # found a digital input servic, now check the type
+ if service.startswith ("com.victronenergy.digitalinput"):
+ self._digitalInputTypeObject = TheBus.get_object (service, '/Type')
+ # found it!
+ if self._digitalInputTypeObject.GetValue() == 9:
+ newInputService = service
+ break
+
+ # found new service - get objects for use later
+ if newInputService != "":
+ self.log_info ("Found generator digital input service at %s" % newInputService)
+ self._generatorInputStateObject = TheBus.get_object(newInputService, '/State')
+ else:
+ if self._generatorInputStateObject != None:
+ self.log_info ("Generator digital input service NOT found")
+ self._generatorInputStateObject = None
+ self._digitalInputTypeObject = None
+ self._searchDelay = 0 # start delay timer
+
+ # if serch delay timer is active, increment it now
+ if self._searchDelay <= 10:
+ self._searchDelay += 1
+
+
+ # collect generator input states
+ inputState = '?'
+ # if generator digital input is present, use that
+ if self._generatorInputStateObject != None:
+ inputState = self._generatorInputStateObject.GetValue ()
+ if inputState == 10:
+ inputState = 'R'
+ elif inputState == 11:
+ inputState = 'S'
+ # otherwise use generator AC input to determine running state
+ # use frequency as the test for generator running
+ elif self._ac1_is_generator or self._ac2_is_generator:
+ try:
+ if self._dbusmonitor.get_value (SYSTEM_SERVICE, '/Ac/Genset/Frequency') > 20:
+ inputState = 'R'
+ else:
+ inputState = 'S'
+ except:
+ pass
+
+ # update /GeneratorRunningState
+ if inputState != self._lastState:
+ self._dbusservice['/GeneratorRunningState'] = inputState
+
+ # forward input state changes to /ManualStart
+ if self._linkToExternalState:
+ if inputState == "R" and generatorState == States.STOPPED:
+ self.log_info ("generator was started externally - syncing ManualStart state")
+ self._dbusservice['/ManualStart'] = 1
+ elif inputState == "S" and self._dbusservice['/ManualStart'] == 1 \
+ and generatorState in (States.RUNNING, States.WARMUP, States.COOLDOWN):
+ self.log_info ("generator was stopped externally - syncing ManualStart state")
+ self._dbusservice['/ManualStart'] = 0
+
+ # update /ExternalOverride
+ if inputState == "S" and self._linkToExternalState and generatorState == States.RUNNING:
+ if self._externalOverrideDelay > 5:
+ self._externalOverride = True
+ else:
+ self._externalOverrideDelay += 1
+ else:
+ self._externalOverride = False
+ self._externalOverrideDelay = 0
+
+ if self._externalOverride != self._lastExternalOverride:
+ self._dbusservice['/ExternalOverride'] = self._externalOverride
+ self._lastExternalOverride = self._externalOverride
+
+ except dbus.DBusException:
+ self.log_info ("dbus exception - generator digital input no longer valid")
+ self._generatorInputStateObject = None
+ self._digitalInputTypeObject = None
+ inputState = 0
+
+ self._lastState = inputState
+
+
+#
+# control the accumulaiton of run time based on generator input Running state
+# if the internal state is RUNNING run time is accumulated in self._accumulatedRunTime
+# run time is accumulated if the generator's running state is known to be running or
+# if the generator running state can't be determined
+# the accumulated time dBus parameter and daily and total time accumulators are updated
+# only once everh 60 seconds to minimize processor load
+# if the internal state is STOPPED, one last dBus, daily and total time updates are done
+# then the current time accumulator is cleared
+
+ def _accumulateRunTime (self):
+
+ # grab running state from dBus once, use it many timed below
+
+ if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING): ##########
+ internalRun = True
+ else:
+ internalRun = False
+
+ # if internal state is running, accumulate time if generator is running
+ if internalRun:
+ accumuateTime = True
+ # start new accumulation if not done prevously
+ if self._last_accumulate_time == 0:
+ self._last_accumulate_time = self._currentTime
+
+ # if link to external state is enabled, don't accumulate time if running state is stopped
+ # (accumulate if R or ?)
+ if self._linkToExternalState:
+ try:
+ if self._dbusservice['/GeneratorRunningState'] == 'S':
+ accumuateTime = False
+
+ # if no Forwarder service, allow accumulation
+ except dbus.DBusException:
+ self.log_info ("dBus exception in startstop.py")
+
+ # internal state STOPPED so don't add new time to the accumulation
+ # but there may be time already accumulated that needs to be added to daily and total accumulations
+ else:
+ accumuateTime = False
+
+ # accumulate run time if we passed all the tests above
+ if accumuateTime:
+ self._accumulatedRunTime += self._currentTime - self._last_accumulate_time
+ self._last_accumulate_time = self._currentTime
+
+ # dbus and settings updates trigger time-intensive processing so only do this once every 60 seconds
+ doUpdate = False
+ if internalRun:
+ if self._currentTime - self._last_update_mtime >= 60:
+ doUpdate = True
+ self._last_update_mtime = self._currentTime
+ # it is also done one last time when state is no longer RUNNING
+ elif self._last_update_mtime != 0:
+ doUpdate = True
+
+ if doUpdate:
+ self._update_accumulated_time()
+
+ # stopped - clear the current time accumulator
+ if internalRun == False:
+ self._last_update_mtime = 0
+ self._accumulatedRunTime = 0
+ self._last_accumulate_time = 0
+
+ self._dbusservice['/Runtime'] = int(self._accumulatedRunTime)
+#### end GuiMods
diff --git a/FileSets/v3.50~22/startstop.py.orig b/FileSets/v3.41/startstop.py.orig
similarity index 100%
rename from FileSets/v3.50~22/startstop.py.orig
rename to FileSets/v3.41/startstop.py.orig
diff --git a/FileSets/v3.50~20/COMPLETE b/FileSets/v3.50/COMPLETE
similarity index 100%
rename from FileSets/v3.50~20/COMPLETE
rename to FileSets/v3.50/COMPLETE
diff --git a/FileSets/v3.50/DetailAcInput.qml b/FileSets/v3.50/DetailAcInput.qml
new file mode 120000
index 00000000..66967128
--- /dev/null
+++ b/FileSets/v3.50/DetailAcInput.qml
@@ -0,0 +1 @@
+../v3.51~2/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/DetailInverter.qml b/FileSets/v3.50/DetailInverter.qml
new file mode 120000
index 00000000..674958be
--- /dev/null
+++ b/FileSets/v3.50/DetailInverter.qml
@@ -0,0 +1 @@
+../v3.51~2/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/DetailLoadsCombined.qml b/FileSets/v3.50/DetailLoadsCombined.qml
new file mode 120000
index 00000000..f51dfec1
--- /dev/null
+++ b/FileSets/v3.50/DetailLoadsCombined.qml
@@ -0,0 +1 @@
+../v3.51~2/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/DetailLoadsOnInput.qml b/FileSets/v3.50/DetailLoadsOnInput.qml
new file mode 120000
index 00000000..4813eff7
--- /dev/null
+++ b/FileSets/v3.50/DetailLoadsOnInput.qml
@@ -0,0 +1 @@
+../v3.51~2/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/DetailLoadsOnOutput.qml b/FileSets/v3.50/DetailLoadsOnOutput.qml
new file mode 120000
index 00000000..746a0773
--- /dev/null
+++ b/FileSets/v3.50/DetailLoadsOnOutput.qml
@@ -0,0 +1 @@
+../v3.51~2/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/HubData.qml b/FileSets/v3.50/HubData.qml
new file mode 120000
index 00000000..4321b049
--- /dev/null
+++ b/FileSets/v3.50/HubData.qml
@@ -0,0 +1 @@
+../v3.51~2/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/LINKS_ONLY b/FileSets/v3.50/LINKS_ONLY
similarity index 100%
rename from FileSets/v3.50~20/LINKS_ONLY
rename to FileSets/v3.50/LINKS_ONLY
diff --git a/FileSets/v3.50/ObjectAcConnection.qml b/FileSets/v3.50/ObjectAcConnection.qml
new file mode 120000
index 00000000..a59b2c6d
--- /dev/null
+++ b/FileSets/v3.50/ObjectAcConnection.qml
@@ -0,0 +1 @@
+../v3.51~2/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewAcValuesEnhanced.qml b/FileSets/v3.50/OverviewAcValuesEnhanced.qml
new file mode 120000
index 00000000..06278feb
--- /dev/null
+++ b/FileSets/v3.50/OverviewAcValuesEnhanced.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewFlowComplex.qml b/FileSets/v3.50/OverviewFlowComplex.qml
new file mode 120000
index 00000000..117e095b
--- /dev/null
+++ b/FileSets/v3.50/OverviewFlowComplex.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewGeneratorEnhanced.qml b/FileSets/v3.50/OverviewGeneratorEnhanced.qml
new file mode 120000
index 00000000..7e8fab79
--- /dev/null
+++ b/FileSets/v3.50/OverviewGeneratorEnhanced.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewGeneratorEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.50/OverviewGeneratorRelayEnhanced.qml
new file mode 120000
index 00000000..e4fe32d3
--- /dev/null
+++ b/FileSets/v3.50/OverviewGeneratorRelayEnhanced.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewGridParallel.qml b/FileSets/v3.50/OverviewGridParallel.qml
new file mode 120000
index 00000000..3210810c
--- /dev/null
+++ b/FileSets/v3.50/OverviewGridParallel.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewHub.qml b/FileSets/v3.50/OverviewHub.qml
new file mode 120000
index 00000000..1bfb809c
--- /dev/null
+++ b/FileSets/v3.50/OverviewHub.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewHubEnhanced.qml b/FileSets/v3.50/OverviewHubEnhanced.qml
new file mode 120000
index 00000000..0ee6c316
--- /dev/null
+++ b/FileSets/v3.50/OverviewHubEnhanced.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewMobileEnhanced.qml b/FileSets/v3.50/OverviewMobileEnhanced.qml
new file mode 120000
index 00000000..5055ce73
--- /dev/null
+++ b/FileSets/v3.50/OverviewMobileEnhanced.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/OverviewTanksTempsDigInputs.qml b/FileSets/v3.50/OverviewTanksTempsDigInputs.qml
new file mode 120000
index 00000000..02c45346
--- /dev/null
+++ b/FileSets/v3.50/OverviewTanksTempsDigInputs.qml
@@ -0,0 +1 @@
+../v3.51~2/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/PageSettingsGenerator.qml b/FileSets/v3.50/PageSettingsGenerator.qml
new file mode 120000
index 00000000..bf3cf00e
--- /dev/null
+++ b/FileSets/v3.50/PageSettingsGenerator.qml
@@ -0,0 +1 @@
+../v3.51~2/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/PageSettingsGuiMods.qml b/FileSets/v3.50/PageSettingsGuiMods.qml
new file mode 120000
index 00000000..28eb98af
--- /dev/null
+++ b/FileSets/v3.50/PageSettingsGuiMods.qml
@@ -0,0 +1 @@
+../v3.51~2/PageSettingsGuiMods.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/PageSettingsRelay.qml b/FileSets/v3.50/PageSettingsRelay.qml
new file mode 120000
index 00000000..0872b024
--- /dev/null
+++ b/FileSets/v3.50/PageSettingsRelay.qml
@@ -0,0 +1 @@
+../v3.51~2/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/PowerGauge.qml b/FileSets/v3.50/PowerGauge.qml
new file mode 120000
index 00000000..8c1f5473
--- /dev/null
+++ b/FileSets/v3.50/PowerGauge.qml
@@ -0,0 +1 @@
+../v3.51~2/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/TileDigIn.qml b/FileSets/v3.50/TileDigIn.qml
new file mode 120000
index 00000000..0eab34eb
--- /dev/null
+++ b/FileSets/v3.50/TileDigIn.qml
@@ -0,0 +1 @@
+../v3.51~2/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/TileRelay.qml b/FileSets/v3.50/TileRelay.qml
new file mode 120000
index 00000000..9cf7d223
--- /dev/null
+++ b/FileSets/v3.50/TileRelay.qml
@@ -0,0 +1 @@
+../v3.51~2/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50/dbus_digitalinputs.py b/FileSets/v3.50/dbus_digitalinputs.py
new file mode 120000
index 00000000..5c505fbc
--- /dev/null
+++ b/FileSets/v3.50/dbus_digitalinputs.py
@@ -0,0 +1 @@
+../v3.51~2/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.50/startstop.py b/FileSets/v3.50/startstop.py
new file mode 120000
index 00000000..35d0c9b4
--- /dev/null
+++ b/FileSets/v3.50/startstop.py
@@ -0,0 +1 @@
+../v3.51~2/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.50~20/DetailAcInput.qml b/FileSets/v3.50~20/DetailAcInput.qml
deleted file mode 120000
index cadafa4e..00000000
--- a/FileSets/v3.50~20/DetailAcInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/DetailInverter.qml b/FileSets/v3.50~20/DetailInverter.qml
deleted file mode 120000
index 8a637d73..00000000
--- a/FileSets/v3.50~20/DetailInverter.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/DetailLoadsCombined.qml b/FileSets/v3.50~20/DetailLoadsCombined.qml
deleted file mode 120000
index 5775b2f4..00000000
--- a/FileSets/v3.50~20/DetailLoadsCombined.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/DetailLoadsOnInput.qml b/FileSets/v3.50~20/DetailLoadsOnInput.qml
deleted file mode 120000
index 8486816b..00000000
--- a/FileSets/v3.50~20/DetailLoadsOnInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/DetailLoadsOnOutput.qml b/FileSets/v3.50~20/DetailLoadsOnOutput.qml
deleted file mode 120000
index 5aba1991..00000000
--- a/FileSets/v3.50~20/DetailLoadsOnOutput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/HubData.qml b/FileSets/v3.50~20/HubData.qml
deleted file mode 120000
index c7e782ee..00000000
--- a/FileSets/v3.50~20/HubData.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/ObjectAcConnection.qml b/FileSets/v3.50~20/ObjectAcConnection.qml
deleted file mode 120000
index 2ca36f5e..00000000
--- a/FileSets/v3.50~20/ObjectAcConnection.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewAcValuesEnhanced.qml b/FileSets/v3.50~20/OverviewAcValuesEnhanced.qml
deleted file mode 120000
index 2fcae9eb..00000000
--- a/FileSets/v3.50~20/OverviewAcValuesEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewFlowComplex.qml b/FileSets/v3.50~20/OverviewFlowComplex.qml
deleted file mode 120000
index b8f9a5f4..00000000
--- a/FileSets/v3.50~20/OverviewFlowComplex.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewGeneratorEnhanced.qml b/FileSets/v3.50~20/OverviewGeneratorEnhanced.qml
deleted file mode 120000
index 51b929eb..00000000
--- a/FileSets/v3.50~20/OverviewGeneratorEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/OverviewGeneratorEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.50~20/OverviewGeneratorRelayEnhanced.qml
deleted file mode 120000
index 65979936..00000000
--- a/FileSets/v3.50~20/OverviewGeneratorRelayEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewGridParallel.qml b/FileSets/v3.50~20/OverviewGridParallel.qml
deleted file mode 120000
index 7af3834a..00000000
--- a/FileSets/v3.50~20/OverviewGridParallel.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewHub.qml b/FileSets/v3.50~20/OverviewHub.qml
deleted file mode 120000
index 88267085..00000000
--- a/FileSets/v3.50~20/OverviewHub.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewHubEnhanced.qml b/FileSets/v3.50~20/OverviewHubEnhanced.qml
deleted file mode 120000
index 2e238e43..00000000
--- a/FileSets/v3.50~20/OverviewHubEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewMobileEnhanced.qml b/FileSets/v3.50~20/OverviewMobileEnhanced.qml
deleted file mode 120000
index a753b0fb..00000000
--- a/FileSets/v3.50~20/OverviewMobileEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/OverviewTanksTempsDigInputs.qml b/FileSets/v3.50~20/OverviewTanksTempsDigInputs.qml
deleted file mode 120000
index 5a4aaaeb..00000000
--- a/FileSets/v3.50~20/OverviewTanksTempsDigInputs.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/PageGenerator.qml b/FileSets/v3.50~20/PageGenerator.qml
deleted file mode 120000
index 0b15024e..00000000
--- a/FileSets/v3.50~20/PageGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/PageSettingsGenerator.qml b/FileSets/v3.50~20/PageSettingsGenerator.qml
deleted file mode 120000
index 5e12924e..00000000
--- a/FileSets/v3.50~20/PageSettingsGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/PageSettingsGuiMods.qml b/FileSets/v3.50~20/PageSettingsGuiMods.qml
deleted file mode 120000
index 262f3cec..00000000
--- a/FileSets/v3.50~20/PageSettingsGuiMods.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGuiMods.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/PageSettingsRelay.qml b/FileSets/v3.50~20/PageSettingsRelay.qml
deleted file mode 120000
index 41872500..00000000
--- a/FileSets/v3.50~20/PageSettingsRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/PowerGauge.qml b/FileSets/v3.50~20/PowerGauge.qml
deleted file mode 120000
index 452ca646..00000000
--- a/FileSets/v3.50~20/PowerGauge.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/TileDigIn.qml b/FileSets/v3.50~20/TileDigIn.qml
deleted file mode 120000
index f11f1206..00000000
--- a/FileSets/v3.50~20/TileDigIn.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/TileRelay.qml b/FileSets/v3.50~20/TileRelay.qml
deleted file mode 120000
index 5f86edbf..00000000
--- a/FileSets/v3.50~20/TileRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~20/dbus_digitalinputs.py b/FileSets/v3.50~20/dbus_digitalinputs.py
deleted file mode 120000
index 717bc909..00000000
--- a/FileSets/v3.50~20/dbus_digitalinputs.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.50~20/startstop.py b/FileSets/v3.50~20/startstop.py
deleted file mode 120000
index 42f29aec..00000000
--- a/FileSets/v3.50~20/startstop.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.50~22/DetailAcInput.qml b/FileSets/v3.50~22/DetailAcInput.qml
deleted file mode 120000
index cadafa4e..00000000
--- a/FileSets/v3.50~22/DetailAcInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/DetailInverter.qml b/FileSets/v3.50~22/DetailInverter.qml
deleted file mode 120000
index 8a637d73..00000000
--- a/FileSets/v3.50~22/DetailInverter.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/DetailLoadsCombined.qml b/FileSets/v3.50~22/DetailLoadsCombined.qml
deleted file mode 120000
index 5775b2f4..00000000
--- a/FileSets/v3.50~22/DetailLoadsCombined.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/DetailLoadsOnInput.qml b/FileSets/v3.50~22/DetailLoadsOnInput.qml
deleted file mode 120000
index 8486816b..00000000
--- a/FileSets/v3.50~22/DetailLoadsOnInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/DetailLoadsOnOutput.qml b/FileSets/v3.50~22/DetailLoadsOnOutput.qml
deleted file mode 120000
index 5aba1991..00000000
--- a/FileSets/v3.50~22/DetailLoadsOnOutput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/HubData.qml b/FileSets/v3.50~22/HubData.qml
deleted file mode 120000
index c7e782ee..00000000
--- a/FileSets/v3.50~22/HubData.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/ObjectAcConnection.qml b/FileSets/v3.50~22/ObjectAcConnection.qml
deleted file mode 120000
index 2ca36f5e..00000000
--- a/FileSets/v3.50~22/ObjectAcConnection.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewAcValuesEnhanced.qml b/FileSets/v3.50~22/OverviewAcValuesEnhanced.qml
deleted file mode 120000
index 2fcae9eb..00000000
--- a/FileSets/v3.50~22/OverviewAcValuesEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewFlowComplex.qml b/FileSets/v3.50~22/OverviewFlowComplex.qml
deleted file mode 120000
index b8f9a5f4..00000000
--- a/FileSets/v3.50~22/OverviewFlowComplex.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewGeneratorEnhanced.qml.orig b/FileSets/v3.50~22/OverviewGeneratorEnhanced.qml.orig
deleted file mode 100644
index 89706872..00000000
--- a/FileSets/v3.50~22/OverviewGeneratorEnhanced.qml.orig
+++ /dev/null
@@ -1,147 +0,0 @@
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- id: generator
- title: qsTr("Generator start/stop")
- property string settingsBindPrefix
- property string startStopBindPrefix
- property alias startStopModel: _startStopModel
- property VBusItem activeCondition: VBusItem { bind: Utils.path(startStopBindPrefix, "/RunningByCondition") }
- property VBusItem generatorState: VBusItem { bind: Utils.path(startStopBindPrefix, "/State") }
- property VBusItem runningTime: VBusItem { bind: Utils.path(startStopBindPrefix, "/Runtime") }
- property VBusItem historicalData: VBusItem { bind: Utils.path(settingsBindPrefix, "/AccumulatedDaily") }
-
- FnGeneratorStates {
- id: genState
- }
-
- model: startStopModel
-
- function formatError(text, value)
- {
- return "#" + value.toString() + " " + text
- }
-
- VisibleItemModel {
- id: _startStopModel
-
- MbItemValue {
- description: qsTr("State")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- item.text: activeCondition.valid ? genState.getState(generatorState.value, activeCondition.value) : '---'
- }
-
- MbItemOptions {
- id: _gensetStatus
- description: qsTr("Error")
- bind: Utils.path(startStopBindPrefix, "/Error")
- readonly: true
- show: valid && startStopBindPrefix === "com.victronenergy.generator.startstop0"
- possibleValues: [
- MbOption { description: qsTr("No error"); value: 0 },
- MbOption { description: formatError(qsTr("Remote switch control disabled"), 1); value: 1 },
- MbOption { description: formatError(qsTr("Generator in fault condition"), 2); value: 2 },
- MbOption { description: formatError(qsTr("Generator not detected at AC input"), 3); value: 3 }
- ]
- }
-
- MbItemValue {
- description: qsTr("Run time")
- item.text: runningTime.valid ? Utils.secondsToNoSecsString(runningTime.value) : "0"
- show: generatorState.value in [1, 2, 3] // Running, Warm-up, Cool-down
- }
-
- MbItemValue {
- description: qsTr("Total run time")
- item {
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotal")
- text: Utils.secondsToNoSecsString(item.value - accumulatedTotalOffset.value)
- }
- VBusItem {
- id: accumulatedTotalOffset
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotalOffset")
- }
- }
-
- MbItemValue {
- description: qsTr("Time to service")
- show: item.valid
- item {
- bind: Utils.path(startStopBindPrefix, "/ServiceCounter")
- text: qsTr("%1h").arg((item.value / 60 / 60).toFixed(0))
- }
- }
-
- MbItemValue {
- description: qsTr("Accumulated running time since last test run")
- show: user.accessLevel >= User.AccessService && nextTestRun.show
- backgroundColor: mbStyle.backgroundColorService
- item {
- text: Utils.secondsToNoSecsString(item.value)
- bind: Utils.path(startStopBindPrefix, "/TestRunIntervalRuntime")
- }
- }
-
- MbItemValue {
- id: nextTestRun
- description: qsTr("Time to next test run")
- show: item.valid && item.value > 0
- item {
- text: {
- var remainingTime = item.value - new Date().getTime() / 1000
- if (remainingTime > 0)
- return Utils.secondsToNoSecsString(remainingTime).toString()
- return qsTr("Running now")
- }
- bind: Utils.path(startStopBindPrefix, "/NextTestRun")
- }
- }
-
- MbSwitch {
- name: qsTr("Auto start functionality")
- bind: Utils.path(startStopBindPrefix, "/AutoStartEnabled")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- }
-
- MbSubMenu {
- description: qsTr("Manual start")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- subpage:
- Component {
- PageGeneratorManualStart {
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
-
- MbSubMenu {
- description: qsTr("Daily run time")
- subpage: MbPage {
- // Invert the order
- property variant keys: historicalData.valid ?
- Object.keys(JSON.parse(historicalData.value)).reverse() : 0
-
- title: qsTr("Daily run time")
- focus: active
- model: keys
- delegate: MbItemValue {
- description: Qt.formatDate(new Date(parseInt(keys[index]) * 1000), "dd-MM-yyyy");
- item.text: Utils.secondsToNoSecsString(JSON.parse(historicalData.value)[keys[index]])
- }
- }
- }
-
- MbSubMenu {
- id: conditions
- description: qsTr("Settings")
- subpage: Component {
- PageSettingsGenerator {
- settingsBindPrefix: generator.settingsBindPrefix
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
- }
-}
diff --git a/FileSets/v3.50~22/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.50~22/OverviewGeneratorRelayEnhanced.qml
deleted file mode 120000
index 65979936..00000000
--- a/FileSets/v3.50~22/OverviewGeneratorRelayEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewGridParallel.qml b/FileSets/v3.50~22/OverviewGridParallel.qml
deleted file mode 120000
index 7af3834a..00000000
--- a/FileSets/v3.50~22/OverviewGridParallel.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewHub.qml b/FileSets/v3.50~22/OverviewHub.qml
deleted file mode 120000
index 88267085..00000000
--- a/FileSets/v3.50~22/OverviewHub.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewHubEnhanced.qml b/FileSets/v3.50~22/OverviewHubEnhanced.qml
deleted file mode 120000
index 2e238e43..00000000
--- a/FileSets/v3.50~22/OverviewHubEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewMobileEnhanced.qml b/FileSets/v3.50~22/OverviewMobileEnhanced.qml
deleted file mode 120000
index a753b0fb..00000000
--- a/FileSets/v3.50~22/OverviewMobileEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/OverviewTanksTempsDigInputs.qml b/FileSets/v3.50~22/OverviewTanksTempsDigInputs.qml
deleted file mode 120000
index 5a4aaaeb..00000000
--- a/FileSets/v3.50~22/OverviewTanksTempsDigInputs.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/PageGenerator.qml b/FileSets/v3.50~22/PageGenerator.qml
deleted file mode 100644
index 2e17cfdd..00000000
--- a/FileSets/v3.50~22/PageGenerator.qml
+++ /dev/null
@@ -1,159 +0,0 @@
-//// changed total time to hours (from varilable format)
-
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- id: generator
- title: qsTr("Generator start/stop")
- property string settingsBindPrefix
- property string startStopBindPrefix
- property alias startStopModel: _startStopModel
- property VBusItem activeCondition: VBusItem { bind: Utils.path(startStopBindPrefix, "/RunningByCondition") }
- property VBusItem generatorState: VBusItem { bind: Utils.path(startStopBindPrefix, "/State") }
- property VBusItem runningTime: VBusItem { bind: Utils.path(startStopBindPrefix, "/Runtime") }
- property VBusItem historicalData: VBusItem { bind: Utils.path(settingsBindPrefix, "/AccumulatedDaily") }
-
- FnGeneratorStates {
- id: genState
- }
-
-//// changed total time to hours (from varilable format)
- function formatTime (time)
- {
- if (time >= 3600)
- return (time / 3600).toFixed(0) + " h"
- else
- return (time / 60).toFixed(0) + " m"
- }
-
- model: startStopModel
-
- function formatError(text, value)
- {
- return "#" + value.toString() + " " + text
- }
-
- VisibleItemModel {
- id: _startStopModel
-
- MbItemValue {
- description: qsTr("State")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- item.text: activeCondition.valid ? genState.getState(generatorState.value, activeCondition.value) : '---'
- }
-
- MbItemOptions {
- id: _gensetStatus
- description: qsTr("Error")
- bind: Utils.path(startStopBindPrefix, "/Error")
- readonly: true
- show: valid && startStopBindPrefix === "com.victronenergy.generator.startstop0"
- possibleValues: [
- MbOption { description: qsTr("No error"); value: 0 },
- MbOption { description: formatError(qsTr("Remote switch control disabled"), 1); value: 1 },
- MbOption { description: formatError(qsTr("Generator in fault condition"), 2); value: 2 },
- MbOption { description: formatError(qsTr("Generator not detected at AC input"), 3); value: 3 }
- ]
- }
-
- MbItemValue {
- description: qsTr("Run time")
- item.text: runningTime.valid ? Utils.secondsToNoSecsString(runningTime.value) : "0"
- show: generatorState.value in [1, 2, 3] // Running, Warm-up, Cool-down
- }
-
- MbItemValue {
- description: qsTr("Total run time")
- item {
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotal")
- //// changed total time to hours (from varilable format)
- text: formatTime (item.value - accumulatedTotalOffset.value)
- }
- VBusItem {
- id: accumulatedTotalOffset
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotalOffset")
- }
- }
-
- MbItemValue {
- description: qsTr("Time to service")
- show: item.valid
- item {
- bind: Utils.path(startStopBindPrefix, "/ServiceCounter")
- text: qsTr("%1h").arg((item.value / 60 / 60).toFixed(0))
- }
- }
-
- MbItemValue {
- description: qsTr("Accumulated running time since last test run")
- show: user.accessLevel >= User.AccessService && nextTestRun.show
- backgroundColor: mbStyle.backgroundColorService
- item {
- text: Utils.secondsToNoSecsString(item.value)
- bind: Utils.path(startStopBindPrefix, "/TestRunIntervalRuntime")
- }
- }
-
- MbItemValue {
- id: nextTestRun
- description: qsTr("Time to next test run")
- show: item.valid && item.value > 0
- item {
- text: {
- var remainingTime = item.value - new Date().getTime() / 1000
- if (remainingTime > 0)
- return Utils.secondsToNoSecsString(remainingTime).toString()
- return qsTr("Running now")
- }
- bind: Utils.path(startStopBindPrefix, "/NextTestRun")
- }
- }
-
- MbSwitch {
- name: qsTr("Auto start functionality")
- bind: Utils.path(startStopBindPrefix, "/AutoStartEnabled")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- }
-
- MbSubMenu {
- description: qsTr("Manual start")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- subpage:
- Component {
- PageGeneratorManualStart {
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
-
- MbSubMenu {
- description: qsTr("Daily run time")
- subpage: MbPage {
- // Invert the order
- property variant keys: historicalData.valid ?
- Object.keys(JSON.parse(historicalData.value)).reverse() : 0
-
- title: qsTr("Daily run time")
- focus: active
- model: keys
- delegate: MbItemValue {
- description: Qt.formatDate(new Date(parseInt(keys[index]) * 1000), "dd-MM-yyyy");
- item.text: Utils.secondsToNoSecsString(JSON.parse(historicalData.value)[keys[index]])
- }
- }
- }
-
- MbSubMenu {
- id: conditions
- description: qsTr("Settings")
- subpage: Component {
- PageSettingsGenerator {
- settingsBindPrefix: generator.settingsBindPrefix
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
- }
-}
diff --git a/FileSets/v3.50~22/PageGenerator.qml.orig b/FileSets/v3.50~22/PageGenerator.qml.orig
deleted file mode 100644
index 89706872..00000000
--- a/FileSets/v3.50~22/PageGenerator.qml.orig
+++ /dev/null
@@ -1,147 +0,0 @@
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- id: generator
- title: qsTr("Generator start/stop")
- property string settingsBindPrefix
- property string startStopBindPrefix
- property alias startStopModel: _startStopModel
- property VBusItem activeCondition: VBusItem { bind: Utils.path(startStopBindPrefix, "/RunningByCondition") }
- property VBusItem generatorState: VBusItem { bind: Utils.path(startStopBindPrefix, "/State") }
- property VBusItem runningTime: VBusItem { bind: Utils.path(startStopBindPrefix, "/Runtime") }
- property VBusItem historicalData: VBusItem { bind: Utils.path(settingsBindPrefix, "/AccumulatedDaily") }
-
- FnGeneratorStates {
- id: genState
- }
-
- model: startStopModel
-
- function formatError(text, value)
- {
- return "#" + value.toString() + " " + text
- }
-
- VisibleItemModel {
- id: _startStopModel
-
- MbItemValue {
- description: qsTr("State")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- item.text: activeCondition.valid ? genState.getState(generatorState.value, activeCondition.value) : '---'
- }
-
- MbItemOptions {
- id: _gensetStatus
- description: qsTr("Error")
- bind: Utils.path(startStopBindPrefix, "/Error")
- readonly: true
- show: valid && startStopBindPrefix === "com.victronenergy.generator.startstop0"
- possibleValues: [
- MbOption { description: qsTr("No error"); value: 0 },
- MbOption { description: formatError(qsTr("Remote switch control disabled"), 1); value: 1 },
- MbOption { description: formatError(qsTr("Generator in fault condition"), 2); value: 2 },
- MbOption { description: formatError(qsTr("Generator not detected at AC input"), 3); value: 3 }
- ]
- }
-
- MbItemValue {
- description: qsTr("Run time")
- item.text: runningTime.valid ? Utils.secondsToNoSecsString(runningTime.value) : "0"
- show: generatorState.value in [1, 2, 3] // Running, Warm-up, Cool-down
- }
-
- MbItemValue {
- description: qsTr("Total run time")
- item {
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotal")
- text: Utils.secondsToNoSecsString(item.value - accumulatedTotalOffset.value)
- }
- VBusItem {
- id: accumulatedTotalOffset
- bind: Utils.path(settingsBindPrefix, "/AccumulatedTotalOffset")
- }
- }
-
- MbItemValue {
- description: qsTr("Time to service")
- show: item.valid
- item {
- bind: Utils.path(startStopBindPrefix, "/ServiceCounter")
- text: qsTr("%1h").arg((item.value / 60 / 60).toFixed(0))
- }
- }
-
- MbItemValue {
- description: qsTr("Accumulated running time since last test run")
- show: user.accessLevel >= User.AccessService && nextTestRun.show
- backgroundColor: mbStyle.backgroundColorService
- item {
- text: Utils.secondsToNoSecsString(item.value)
- bind: Utils.path(startStopBindPrefix, "/TestRunIntervalRuntime")
- }
- }
-
- MbItemValue {
- id: nextTestRun
- description: qsTr("Time to next test run")
- show: item.valid && item.value > 0
- item {
- text: {
- var remainingTime = item.value - new Date().getTime() / 1000
- if (remainingTime > 0)
- return Utils.secondsToNoSecsString(remainingTime).toString()
- return qsTr("Running now")
- }
- bind: Utils.path(startStopBindPrefix, "/NextTestRun")
- }
- }
-
- MbSwitch {
- name: qsTr("Auto start functionality")
- bind: Utils.path(startStopBindPrefix, "/AutoStartEnabled")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- }
-
- MbSubMenu {
- description: qsTr("Manual start")
- show: startStopBindPrefix === "com.victronenergy.generator.startstop0"
- subpage:
- Component {
- PageGeneratorManualStart {
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
-
- MbSubMenu {
- description: qsTr("Daily run time")
- subpage: MbPage {
- // Invert the order
- property variant keys: historicalData.valid ?
- Object.keys(JSON.parse(historicalData.value)).reverse() : 0
-
- title: qsTr("Daily run time")
- focus: active
- model: keys
- delegate: MbItemValue {
- description: Qt.formatDate(new Date(parseInt(keys[index]) * 1000), "dd-MM-yyyy");
- item.text: Utils.secondsToNoSecsString(JSON.parse(historicalData.value)[keys[index]])
- }
- }
- }
-
- MbSubMenu {
- id: conditions
- description: qsTr("Settings")
- subpage: Component {
- PageSettingsGenerator {
- settingsBindPrefix: generator.settingsBindPrefix
- startStopBindPrefix: generator.startStopBindPrefix
- }
- }
- }
- }
-}
diff --git a/FileSets/v3.50~22/PageSettingsGenerator.qml b/FileSets/v3.50~22/PageSettingsGenerator.qml
deleted file mode 100644
index 2498ea6e..00000000
--- a/FileSets/v3.50~22/PageSettingsGenerator.qml
+++ /dev/null
@@ -1,127 +0,0 @@
-//// GuiMods
-//// added link to external state enable
-
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- id: root
- title: qsTr("Generator start/stop settings")
- property string settingsBindPrefix
- property string startStopBindPrefix
- property VBusItem acIn1Source: VBusItem { bind: "com.victronenergy.settings/Settings/SystemSetup/AcInput1" }
- property VBusItem acIn2Source: VBusItem { bind: "com.victronenergy.settings/Settings/SystemSetup/AcInput2" }
- property VBusItem capabilities: VBusItem { bind: Utils.path(startStopBindPrefix, "/Capabilities") }
- property int warmupCapability: 1
-
- model: VisibleItemModel {
-
- MbSubMenu {
- id: conditions
- description: qsTr("Conditions")
- subpage:
- Component {
- PageGeneratorConditions {
- title: qsTr("Conditions")
- bindPrefix: root.settingsBindPrefix
- startStopBindPrefix: root.startStopBindPrefix
- }
- }
- }
-
- MbSpinBox {
- description: qsTr("Minimum run time")
- item {
- bind: Utils.path(settingsBindPrefix, "/MinimumRuntime")
- unit: "m"
- decimals: 0
- step: 1
- }
- }
-
- MbSubMenu {
- show: capabilities.value & warmupCapability
- description: qsTr("Warm-up & cool-down")
- subpage:
- Component {
- PageSettingsGeneratorWarmup {
- title: qsTr("Warm-up & cool-down")
- }
- }
- }
-
- MbSwitch {
- property bool generatorIsSet: acIn1Source.value === 2 || acIn2Source.value === 2
- name: qsTr("Detect generator at AC input")
- bind: Utils.path(settingsBindPrefix, "/Alarms/NoGeneratorAtAcIn")
- enabled: valid && (generatorIsSet || checked)
- onClicked: {
- if (!checked) {
- if (!generatorIsSet) {
- toast.createToast(qsTr("None of the AC inputs is set to generator. Go to the system setup page and set the correct " +
- "AC input to generator in order to enable this functionality."), 10000, "icon-info-active")
- } else {
- toast.createToast(qsTr("An alarm will be triggered when no power from the generator is detected at the inverter AC input. " +
- "Make sure that the correct AC input is set to generator on the system setup page."), 12000, "icon-info-active")
- }
- }
- }
- }
-//// GuiMods
- MbSwitch {
- name: qsTr("Link to external running state")
- bind: Utils.path(settingsBindPrefix, "/LinkToExternalStatus")
- onClicked:
- {
- if (!checked)
- toast.createToast(qsTr("Manual run will be synchronized with the generaror 'is running digital input' or AC input"), 10000, "icon-info-active")
- }
- }
-
- MbSwitch {
- name: qsTr("Alarm when generator is not in auto start mode")
- bind: Utils.path(settingsBindPrefix, "/Alarms/AutoStartDisabled")
- onClicked: {
- if (!checked) {
- toast.createToast(qsTr("An alarm will be triggered when auto start function is left disabled for more than 10 minutes."), 12000, "icon-info-active")
- }
- }
- }
-
- MbSwitch {
- id: timeZones
- name: qsTr("Quiet hours")
- bind: Utils.path(settingsBindPrefix, "/QuietHours/Enabled")
- enabled: valid
- writeAccessLevel: User.AccessUser
- }
-
- MbEditBoxTime {
- description: qsTr("Quiet hours start time")
- item.bind: Utils.path(settingsBindPrefix, "/QuietHours/StartTime")
- show: timeZones.checked
- writeAccessLevel: User.AccessUser
- }
-
- MbEditBoxTime {
- description: qsTr("Quiet hours end time")
- item.bind: Utils.path(settingsBindPrefix, "/QuietHours/EndTime")
- show: timeZones.checked
- writeAccessLevel: User.AccessUser
- }
-
- MbSubMenu {
- id: runtimePage
- description: qsTr("Run time and service")
- subpage:
- Component {
- PageGeneratorRuntimeService {
- title: qsTr("Run time and service")
- startStopBindPrefix: root.startStopBindPrefix
- settingsBindPrefix: root.settingsBindPrefix
- }
- }
- }
- }
-}
diff --git a/FileSets/v3.50~22/PageSettingsGuiMods.qml.orig b/FileSets/v3.50~22/PageSettingsGuiMods.qml.orig
deleted file mode 100644
index b0ccbd51..00000000
--- a/FileSets/v3.50~22/PageSettingsGuiMods.qml.orig
+++ /dev/null
@@ -1,356 +0,0 @@
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- property string cgwacsPath: "com.victronenergy.settings/Settings/CGwacs"
- property string settingsPrefix: "com.victronenergy.settings"
- property string batteryLifePath: cgwacsPath + "/BatteryLife"
- // Hub4Mode
- property int hub4PhaseCompensation: 1
- property int hub4PhaseSplit: 2
- property int hub4Disabled: 3
- // BatteryLifeState
- property int batteryLifeStateDisabled: 0
- property int batteryLifeStateRestart: 1
- property int batteryLifeStateDefault: 2
- property int batteryLifeStateAbsorption: 3
- property int batteryLifeStateFloat: 4
- property int batteryLifeStateDischarged: 5
- property int batteryLifeStateForceCharge: 6
- property int batteryLifeStateSustain: 7
- property int batteryLifeStateLowSocCharge: 8
- property int batteryKeepCharged: 9
- property int batterySocGuardDefault: 10
- property int batterySocGuardDischarged: 11
- property int batterySocGuardLowSocCharge: 12
-
- property VBusItem systemType: VBusItem { bind: "com.victronenergy.system/SystemType" }
- property VBusItem maxChargePowerItem: VBusItem { bind: Utils.path(cgwacsPath, "/MaxChargePower") }
- property VBusItem maxDischargePowerItem: VBusItem { bind: Utils.path(cgwacsPath, "/MaxDischargePower") }
- property VBusItem socLimitItem: VBusItem { bind: Utils.path(batteryLifePath, "/SocLimit") }
- property VBusItem minSocLimitItem: VBusItem { bind: Utils.path(batteryLifePath, "/MinimumSocLimit") }
- property VBusItem stateItem: VBusItem { bind: Utils.path(batteryLifePath, "/State") }
- property VBusItem hub4Mode: VBusItem { bind: Utils.path(cgwacsPath, "/Hub4Mode") }
- property VBusItem maxChargeCurrentControl: VBusItem { bind: "com.victronenergy.system/Control/MaxChargeCurrent" }
- property VBusItem scheduleSoc: VBusItem { bind: "com.victronenergy.system/Control/ScheduledSoc" }
- property VBusItem dEssModeItem: VBusItem { bind: "com.victronenergy.settings/Settings/DynamicEss/Mode" }
-
- title: systemType.value === "Hub-4" ? systemType.value : qsTr("ESS")
- model: systemType.value === "ESS" || systemType.value === "Hub-4" ? hub4Settings : noHub4
-
- VisibleItemModel {
- id: noHub4
-
- MbItemText {
- text: qsTr("No ESS Assistant found")
- }
- }
-
- function isBatteryLifeActive(state) {
- switch (state) {
- case batteryLifeStateRestart:
- case batteryLifeStateDefault:
- case batteryLifeStateAbsorption:
- case batteryLifeStateFloat:
- case batteryLifeStateDischarged:
- case batteryLifeStateForceCharge:
- case batteryLifeStateSustain:
- case batteryLifeStateLowSocCharge:
- return true
- default:
- return false
- }
- }
-
- function isBatterySocGuardActive(state) {
- switch (state) {
- case batterySocGuardDefault:
- case batterySocGuardDischarged:
- case batterySocGuardLowSocCharge:
- return true
- default:
- return false
- }
- }
-
- VisibleItemModel {
- id: hub4Settings
-
- MbItemOptions {
- function getLocalValue(hub4Mode, state) {
- if (hub4Mode === undefined || state === undefined)
- return undefined
- if (hub4Mode === hub4Disabled)
- return 3
- if (isBatteryLifeActive(state))
- return 0
- if (isBatterySocGuardActive(state))
- return 1
- if (state === batteryKeepCharged)
- return 2
- return 0
- }
-
- description: qsTr("Mode")
- localValue: getLocalValue(hub4Mode.value, stateItem.value)
- possibleValues:[
- MbOption { description: qsTr("Optimized (with BatteryLife)"); value: 0 },
- MbOption { description: qsTr("Optimized (without BatteryLife)"); value: 1 },
- MbOption { description: qsTr("Keep batteries charged"); value: 2 },
- MbOption { description: qsTr("External control"); value: 3 }
- ]
- onLocalValueChanged: {
- if (localValue === undefined)
- return
- // Hub 4 mode
- if (localValue === 3 && hub4Mode.value !== hub4Disabled) {
- hub4Mode.setValue(hub4Disabled)
- } else if (localValue !== 3 && hub4Mode.value === hub4Disabled) {
- hub4Mode.setValue(hub4PhaseCompensation)
- }
- // BatteryLife state
- switch (localValue) {
- case 0:
- if (!isBatteryLifeActive(stateItem.value))
- stateItem.setValue(batteryLifeStateRestart)
- break
- case 1:
- if (!isBatterySocGuardActive(stateItem.value))
- stateItem.setValue(batterySocGuardDefault)
- break
- case 2:
- stateItem.setValue(batteryKeepCharged)
- break
- case 3:
- stateItem.setValue(batteryLifeStateDisabled)
- break
- }
- }
- }
-
- MbItemOptions {
- id: withoutGridMeter
- description: qsTr("Grid metering")
- bind: Utils.path(cgwacsPath, '/RunWithoutGridMeter')
- show: hub4Mode.value !== hub4Disabled
- enabled: userHasWriteAccess
- possibleValues:[
- MbOption { description: qsTr("External meter"); value: 0 },
- MbOption { description: qsTr("Inverter/Charger"); value: 1 }
- ]
- }
-
- MbSwitch {
- id: acOutInUse
- bind: Utils.path(settingsPrefix, "/Settings/SystemSetup/HasAcOutSystem")
- name: qsTr("Inverter AC output in use")
- show: withoutGridMeter.value == 0
- }
-
- MbItemOptions {
- description: qsTr("Self-consumption from battery")
- bind: Utils.path(cgwacsPath, "/BatteryUse")
- show: withoutGridMeter.value == 0 && acOutInUse.item.value == 1
- possibleValues:[
- MbOption { description: qsTr("All system loads"); value: 0 },
- MbOption { description: qsTr("Only critical loads"); value: 1 }
- ]
- }
-
- MbItemOptions {
- description: qsTr("Multiphase regulation")
- bind: hub4Mode.bind
- show: hub4Mode.value !== hub4Disabled && stateItem.value !== batteryKeepCharged
- enabled: userHasWriteAccess
- possibleValues:[
- MbOption { description: qsTr("Total of all phases"); value: hub4PhaseCompensation },
- MbOption { description: qsTr("Individual phase"); value: hub4PhaseSplit }
- ]
- onOptionSelected: {
- if (newValue === hub4PhaseSplit) {
- toast.createToast(qsTr("Each phase is regulated to individually achieve the grid setpoint (system efficiency is decreased).\n\n" +
- "CAUTION: Use only if required by the utility provider"), 15000);
- } else if (newValue === hub4PhaseCompensation) {
- toast.createToast(qsTr("The total of all phases is intelligently regulated to achieve the grid setpoint (system efficiency is optimised).\n\n" +
- "Use unless prohibited by the utility provider"), 15000);
- }
- }
- }
-
- MbSpinBox {
- id: minSocLimit
- description: qsTr("Minimum SOC (unless grid fails)")
- enabled: userHasWriteAccess
- show: hub4Mode.value !== hub4Disabled && stateItem.value !== batteryKeepCharged
- item {
- bind: Utils.path(batteryLifePath, "/MinimumSocLimit")
- decimals: 0
- unit: "%"
- min: 0
- max: 100
- step: 5
- }
- }
-
- MbItemValue {
- id: socLimit
- description: qsTr("Active SOC limit")
- show: hub4Mode.value !== hub4Disabled && isBatteryLifeActive(stateItem.value)
- item {
- value: Math.max(minSocLimitItem.value, socLimitItem.value)
- unit: '%'
- }
- }
-
- MbItemOptions {
- description: qsTr("BatteryLife state")
- value: stateItem.value
- readonly: true
- show: hub4Mode.value !== hub4Disabled && isBatteryLifeActive(stateItem.value)
- possibleValues:[
- // Values below taken from MaintenanceState enum in dbus-cgwacs
- MbOption { description: qsTr("Self-consumption"); value: 2 },
- MbOption { description: qsTr("Self-consumption"); value: 3 },
- MbOption { description: qsTr("Self-consumption"); value: 4 },
- MbOption { description: qsTr("Discharge disabled"); value: 5 },
- MbOption { description: qsTr("Slow charge"); value: 6 },
- MbOption { description: qsTr("Sustain"); value: 7 },
- MbOption { description: qsTr("Recharge"); value: 8 }
- ]
- }
-
- MbSwitch {
- id: maxChargePowerSwitch
- name: qsTr("Limit charge power")
- checked: maxChargePowerItem.value >= 0
- enabled: userHasWriteAccess
- show: hub4Mode.value !== hub4Disabled && !(maxChargeCurrentControl.valid && maxChargeCurrentControl.value)
- onCheckedChanged: {
- if (checked && maxChargePowerItem.value < 0)
- maxChargePowerItem.setValue(1000)
- else if (!checked && maxChargePowerItem.value >= 0)
- maxChargePowerItem.setValue(-1)
- }
- }
-
- MbSpinBox {
- id: maxChargePower
- description: qsTr("Maximum charge power")
- enabled: userHasWriteAccess
- show: maxChargePowerSwitch.show && maxChargePowerSwitch.checked
- item {
- bind: Utils.path(cgwacsPath, "/MaxChargePower")
- decimals: 0
- unit: "W"
- min: 0
- max: 200000
- step: 50
- }
- }
-
- MbSwitch {
- id: maxInverterPowerSwitch
- name: qsTr("Limit inverter power")
- checked: maxDischargePowerItem.value >= 0
- enabled: userHasWriteAccess
- show: hub4Mode.value !== hub4Disabled && stateItem.value !== batteryKeepCharged
- onCheckedChanged: {
- if (checked && maxDischargePowerItem.value < 0)
- maxDischargePowerItem.setValue(1000)
- else if (!checked && maxDischargePowerItem.value >= 0)
- maxDischargePowerItem.setValue(-1)
- }
- }
-
- MbSpinBox {
- id: maxDischargePower
- description: qsTr("Maximum inverter power")
- enabled: userHasWriteAccess
- show: maxInverterPowerSwitch.show && maxInverterPowerSwitch.checked
- item {
- bind: Utils.path(cgwacsPath, "/MaxDischargePower")
- decimals: 0
- unit: "W"
- min: 0
- max: 300000
- step: 50
- }
- }
-
- MbSpinBox {
- description: qsTr("Grid setpoint")
- show: hub4Mode.value !== hub4Disabled
- enabled: userHasWriteAccess
- item {
- bind: "com.victronenergy.settings/Settings/CGwacs/AcPowerSetPoint"
- decimals: 0
- unit: "W"
- step: 10
- }
- }
-
- MbSubMenu {
- id: feedinSetupItem
- description: qsTr("Grid feed-in")
- show: hub4Mode.value !== hub4Disabled
- subpage: Component {
- PageSettingsHub4Feedin {
- title: feedinSetupItem.description
- }
- }
- }
-
- MbSubMenu {
- id: peakShaveSetupMenu
- description: qsTr("Peak shaving")
- show: hub4Mode.value !== hub4Disabled
- subpage: Component {
- PageSettingsHub4Peakshaving {
- title: peakShaveSetupMenu.description
- }
- }
- }
-
- MbSubMenu {
- id: scheduleSettings
- property string bindPrefix: "com.victronenergy.settings/Settings/CGwacs/BatteryLife/Schedule/Charge/"
- description: qsTr("Scheduled charge levels")
- show: hub4Mode.value !== hub4Disabled && stateItem.value !== batteryKeepCharged
- item: VBusItem { value: scheduleSoc.valid ? qsTr("Active (%1)").arg(scheduleSoc.text) : qsTr("Inactive") }
- subpage: Component {
- MbPage {
- title: scheduleSettings.description
- model: VisibleItemModel {
- ChargeScheduleItem { bindPrefix: scheduleSettings.bindPrefix; scheduleNumber: 0 }
- ChargeScheduleItem { bindPrefix: scheduleSettings.bindPrefix; scheduleNumber: 1 }
- ChargeScheduleItem { bindPrefix: scheduleSettings.bindPrefix; scheduleNumber: 2 }
- ChargeScheduleItem { bindPrefix: scheduleSettings.bindPrefix; scheduleNumber: 3 }
- ChargeScheduleItem { bindPrefix: scheduleSettings.bindPrefix; scheduleNumber: 4 }
- }
- }
- }
- }
-
- MbSubMenu {
- id: dEssSetupItem
- description: qsTr("Dynamic ESS")
- show: (dEssModeItem.value > 0 || user.accessLevel >= User.AccessService) && hub4Mode.value !== hub4Disabled && stateItem.value !== batteryKeepCharged
- subpage: Component {
- PageSettingsDynamicEss {
- title: dEssSetupItem.description
- }
- }
- }
-
- MbSubMenu {
- id: deviceItem
- description: qsTr("Debug")
- show: hub4Mode.value !== hub4Disabled && user.accessLevel >= User.AccessService
- backgroundColor: mbStyle.backgroundColorService
- subpage: Component {
- PageHub4Debug { }
- }
- }
- }
-}
diff --git a/FileSets/v3.50~22/PageSettingsRelay.qml b/FileSets/v3.50~22/PageSettingsRelay.qml
deleted file mode 100644
index 60d9d900..00000000
--- a/FileSets/v3.50~22/PageSettingsRelay.qml
+++ /dev/null
@@ -1,515 +0,0 @@
-//////// modified to
-//////// add up to 18 relays
-//////// custom relay name for Relay Overview
-//////// show/hide relay in Relay Overview
-
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-
-MbPage {
- id: pageRelaySettings
- title: qsTr("Relay")
- property string bindPrefix: "com.victronenergy.settings"
- property VBusItem relay1Item: VBusItem { bind: "com.victronenergy.system/Relay/0/State" }
- property bool hasRelay1: relay1Item.valid
- property VBusItem relay2Item: VBusItem { bind: "com.victronenergy.system/Relay/1/State" }
- property bool hasRelay2: relay2Item.valid
- property VBusItem relay3Item: VBusItem { bind: "com.victronenergy.system/Relay/2/State" }
- property bool hasRelay3: relay3Item.valid
- property VBusItem relay4Item: VBusItem { bind: "com.victronenergy.system/Relay/3/State" }
- property bool hasRelay4: relay4Item.valid
- property VBusItem relay5Item: VBusItem { bind: "com.victronenergy.system/Relay/4/State" }
- property bool hasRelay5: relay5Item.valid
- property VBusItem relay6Item: VBusItem { bind: "com.victronenergy.system/Relay/5/State" }
- property bool hasRelay6: relay6Item.valid
- property VBusItem relay7Item: VBusItem { bind: "com.victronenergy.system/Relay/6/State" }
- property bool hasRelay7: relay7Item.valid
- property VBusItem relay8Item: VBusItem { bind: "com.victronenergy.system/Relay/7/State" }
- property bool hasRelay8: relay8Item.valid
- property VBusItem relay9Item: VBusItem { bind: "com.victronenergy.system/Relay/8/State" }
- property bool hasRelay9: relay9Item.valid
- property VBusItem relay10Item: VBusItem { bind: "com.victronenergy.system/Relay/9/State" }
- property bool hasRelay10: relay10Item.valid
- property VBusItem relay11Item: VBusItem { bind: "com.victronenergy.system/Relay/10/State" }
- property bool hasRelay11: relay11Item.valid
- property VBusItem relay12Item: VBusItem { bind: "com.victronenergy.system/Relay/11/State" }
- property bool hasRelay12: relay12Item.valid
- property VBusItem relay13Item: VBusItem { bind: "com.victronenergy.system/Relay/12/State" }
- property bool hasRelay13: relay13Item.valid
- property VBusItem relay14Item: VBusItem { bind: "com.victronenergy.system/Relay/13/State" }
- property bool hasRelay14: relay14Item.valid
- property VBusItem relay15Item: VBusItem { bind: "com.victronenergy.system/Relay/14/State" }
- property bool hasRelay15: relay15Item.valid
- property VBusItem relay16Item: VBusItem { bind: "com.victronenergy.system/Relay/15/State" }
- property bool hasRelay16: relay16Item.valid
- property VBusItem relay17Item: VBusItem { bind: "com.victronenergy.system/Relay/16/State" }
- property bool hasRelay17: relay17Item.valid
- property VBusItem relay18Item: VBusItem { bind: "com.victronenergy.system/Relay/17/State" }
- property bool hasRelay18: relay18Item.valid
-
- property VBusItem relay1NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/0/CustomName") }
- property VBusItem relay2NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/1/CustomName") }
- property VBusItem relay3NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/2/CustomName") }
- property VBusItem relay4NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/3/CustomName") }
- property VBusItem relay5NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/4/CustomName") }
- property VBusItem relay6NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/5/CustomName") }
- property VBusItem relay7NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/6/CustomName") }
- property VBusItem relay8NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/7/CustomName") }
- property VBusItem relay9NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/8/CustomName") }
- property VBusItem relay10NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/9/CustomName") }
- property VBusItem relay11NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/10/CustomName") }
- property VBusItem relay12NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/11/CustomName") }
- property VBusItem relay13NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/12/CustomName") }
- property VBusItem relay14NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/13/CustomName") }
- property VBusItem relay15NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/14/CustomName") }
- property VBusItem relay16NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/15/CustomName") }
- property VBusItem relay17NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/16/CustomName") }
- property VBusItem relay18NameItem: VBusItem { bind: Utils.path(bindPrefix, "/Settings/Relay/17/CustomName") }
-
- function relayName (nameItem, relayNumber)
- {
- var prefix, suffix
- if (nameItem.valid && nameItem.value != "")
- {
- prefix = nameItem.value + " ("
- suffix = ")"
- }
- else
- {
- prefix = ""
- suffix = ""
- }
- if (relayNumber == 1)
- return prefix + (hasRelay2 ? qsTr("Relay 1") : qsTr("Relay")) + suffix + " " + qsTr("On")
- else
- return prefix + qsTr("Relay") + " " + relayNumber + suffix + " " + qsTr("On")
- }
-
- model: VisibleItemModel {
- MbItemOptions {
- id: relay1Function
- description: hasRelay2 ? qsTr("Function (Relay 1)") : qsTr("Function")
- bind: Utils.path(bindPrefix, "/Settings/Relay/Function")
- possibleValues:[
- MbOption { description: qsTr("Alarm relay"); value: 0 },
- MbOption { description: qsTr("Generator start/stop"); value: 1 },
- MbOption { description: qsTr("Tank pump"); value: 3 },
- MbOption { description: qsTr("Manual"); value: 2 },
- MbOption { description: qsTr("Temperature"); value: 4 }
- ]
- show: hasRelay1
- }
-
- MbItemOptions {
- description: qsTr("Alarm relay polarity")
- bind: Utils.path(bindPrefix, "/Settings/Relay/Polarity")
- show: hasRelay1 && relay1Function.value === 0
- possibleValues: [
- MbOption { description: qsTr("Normally open"); value: 0 },
- MbOption { description: qsTr("Normally closed"); value: 1 }
- ]
- }
-
- MbSwitch {
- id: manualSwitch1
- name: relayName (relay1NameItem, 1)
- bind: "com.victronenergy.system/Relay/0/State"
- show: hasRelay1 && relay1Function.value === 2 // manual mode
- }
-
- MbItemOptions {
- id: relay2Function
- description: hasRelay2 ? qsTr("Function (Relay 2)") : qsTr("Function")
- bind: Utils.path(bindPrefix, "/Settings/Relay/1/Function")
- show: hasRelay2
- possibleValues:[
- MbOption { description: qsTr("Manual"); value: 2 },
- MbOption { description: qsTr("Temperature"); value: 4 }
- ]
- }
- MbSwitch {
- id: manualSwitch2
- name: relayName (relay2NameItem, 2)
- bind: "com.victronenergy.system/Relay/1/State"
- show: hasRelay2 && relay2Function.value === 2
- }
- MbSwitch {
- id: manualSwitch3
- name: relayName (relay3NameItem, 3)
- bind: "com.victronenergy.system/Relay/2/State"
- show: hasRelay3
- }
- MbSwitch {
- id: manualSwitch4
- name: relayName (relay4NameItem, 4)
- bind: "com.victronenergy.system/Relay/3/State"
- show: hasRelay4
- }
- MbSwitch {
- id: manualSwitch5
- name: relayName (relay5NameItem, 5)
- bind: "com.victronenergy.system/Relay/4/State"
- show: hasRelay5
- }
- MbSwitch {
- id: manualSwitch6
- name: relayName (relay6NameItem, 6)
- bind: "com.victronenergy.system/Relay/5/State"
- show: hasRelay6
- }
- MbSwitch {
- id: manualSwitch7
- name: relayName (relay7NameItem, 7)
- bind: "com.victronenergy.system/Relay/6/State"
- show: hasRelay7
- }
- MbSwitch {
- id: manualSwitch8
- name: relayName (relay8NameItem, 8)
- bind: "com.victronenergy.system/Relay/7/State"
- show: hasRelay8
- }
- MbSwitch {
- id: manualSwitch9
- name: relayName (relay9NameItem, 9)
- bind: "com.victronenergy.system/Relay/8/State"
- show: hasRelay9
- }
- MbSwitch {
- id: manualSwitch10
- name: relayName (relay10NameItem, 10)
- bind: "com.victronenergy.system/Relay/9/State"
- show: hasRelay10
- }
- MbSwitch {
- id: manualSwitch11
- name: relayName (relay11NameItem, 11)
- bind: "com.victronenergy.system/Relay/10/State"
- show: hasRelay11
- }
- MbSwitch {
- id: manualSwitch12
- name: relayName (relay12NameItem, 12)
- bind: "com.victronenergy.system/Relay/11/State"
- show: hasRelay12
- }
- MbSwitch {
- id: manualSwitch13
- name: relayName (relay13NameItem, 13)
- bind: "com.victronenergy.system/Relay/12/State"
- show: hasRelay13
- }
- MbSwitch {
- id: manualSwitch14
- name: relayName (relay14NameItem, 14)
- bind: "com.victronenergy.system/Relay/13/State"
- show: hasRelay14
- }
- MbSwitch {
- id: manualSwitch15
- name: relayName (relay15NameItem, 15)
- bind: "com.victronenergy.system/Relay/14/State"
- show: hasRelay15
- }
- MbSwitch {
- id: manualSwitch16
- name: relayName (relay16NameItem, 16)
- bind: "com.victronenergy.system/Relay/15/State"
- show: hasRelay16
- }
- MbSwitch {
- id: manualSwitch17
- name: relayName (relay17NameItem, 17)
- bind: "com.victronenergy.system/Relay/16/State"
- show: hasRelay17
- }
- MbSwitch {
- id: manualSwitch18
- name: relayName (relay18NameItem, 18)
- bind: "com.victronenergy.system/Relay/17/State"
- show: hasRelay18
- }
-
- MbSubMenu {
- id: conditions
- description: qsTr("Temperature control rules")
- show: relay1Function.value === 4 || relay2Function.value === 4
- subpage: Component {
- PageSettingsRelayTempSensors {
- id: relayPage
- title: qsTr("Temperature control rules")
- }
- }
- }
-
- MbEditBox {
- id: relay1name
- description: qsTr("Relay 1 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/0/CustomName"
- show: hasRelay1 && item.valid && relay1Function.value === 2 // manual mode
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay1
- name: qsTr("Show Relay 1 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/0/Show"
- show: hasRelay1
- }
-
- MbEditBox {
- id: relay2name
- description: qsTr("Relay 2 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/1/CustomName"
- show: hasRelay2 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay2
- name: qsTr("Show Relay 2 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/1/Show"
- show: hasRelay2
- }
-
- MbEditBox {
- id: relay3name
- description: qsTr("Relay 3 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/2/CustomName"
- show: hasRelay3 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay3
- name: qsTr("Show Relay 3 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/2/Show"
- show: hasRelay3
- }
-
- MbEditBox {
- id: relay4name
- description: qsTr("Relay 4 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/3/CustomName"
- show: hasRelay4 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay4
- name: qsTr("Show Relay 4 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/3/Show"
- show: hasRelay4
- }
-
- MbEditBox {
- id: relay5name
- description: qsTr("Relay 5 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/4/CustomName"
- show: hasRelay5 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay5
- name: qsTr("Show Relay 5 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/4/Show"
- show: hasRelay5
- }
-
- MbEditBox {
- id: relay6name
- description: qsTr("Relay 6 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/5/CustomName"
- show: hasRelay6 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay6
- name: qsTr("Show Relay 6 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/5/Show"
- show: hasRelay6
- }
-
- MbEditBox {
- id: relay7name
- description: qsTr("Relay 7 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/6/CustomName"
- show: hasRelay7 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay7
- name: qsTr("Show Relay 7 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/6/Show"
- show: hasRelay7
- }
-
- MbEditBox {
- id: relay8name
- description: qsTr("Relay 8 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/7/CustomName"
- show: hasRelay8 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay8
- name: qsTr("Show Relay 8 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/7/Show"
- show: hasRelay8
- }
-
- MbEditBox {
- id: relay9name
- description: qsTr("Relay 9 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/8/CustomName"
- show: hasRelay9 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay9
- name: qsTr("Show Relay 9 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/8/Show"
- show: hasRelay9
- }
-
- MbEditBox {
- id: relay10name
- description: qsTr("Relay 10 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/9/CustomName"
- show: hasRelay10 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay10
- name: qsTr("Show Relay 10 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/9/Show"
- show: hasRelay10
- }
-
- MbEditBox {
- id: relay11name
- description: qsTr("Relay 11 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/10/CustomName"
- show: hasRelay11 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay11
- name: qsTr("Show Relay 11 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/10/Show"
- show: hasRelay11
- }
-
- MbEditBox {
- id: relay12name
- description: qsTr("Relay 12 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/11/CustomName"
- show: hasRelay12 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay12
- name: qsTr("Show Relay 12 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/11/Show"
- show: hasRelay12
- }
-
- MbEditBox {
- id: relay13name
- description: qsTr("Relay 13 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/12/CustomName"
- show: hasRelay13 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay13
- name: qsTr("Show Relay 13 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/12/Show"
- show: hasRelay13
- }
-
- MbEditBox {
- id: relay14name
- description: qsTr("Relay 14 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/13/CustomName"
- show: hasRelay14 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay14
- name: qsTr("Show Relay 14 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/13/Show"
- show: hasRelay14
- }
-
- MbEditBox {
- id: relay15name
- description: qsTr("Relay 15 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/14/CustomName"
- show: hasRelay15 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay15
- name: qsTr("Show Relay 15 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/14/Show"
- show: hasRelay15
- }
-
- MbEditBox {
- id: relay16name
- description: qsTr("Relay 16 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/15/CustomName"
- show: hasRelay16 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay16
- name: qsTr("Show Relay 16 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/15/Show"
- show: hasRelay16
- }
-
- MbEditBox {
- id: relay17name
- description: qsTr("Relay 17 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/16/CustomName"
- show: hasRelay17 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay17
- name: qsTr("Show Relay 17 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/16/Show"
- show: hasRelay17
- }
- MbEditBox {
- id: relay18name
- description: qsTr("Relay 18 Name")
- item.bind: "com.victronenergy.settings/Settings/Relay/17/CustomName"
- show: hasRelay18 && item.valid
- maximumLength: 32
- enableSpaceBar: true
- }
- MbSwitch {
- id: showRelay18
- name: qsTr("Show Relay 18 in overview")
- bind: "com.victronenergy.settings/Settings/Relay/17/Show"
- show: hasRelay18
- }
- }
-}
diff --git a/FileSets/v3.50~22/PowerGauge.qml b/FileSets/v3.50~22/PowerGauge.qml
deleted file mode 120000
index 452ca646..00000000
--- a/FileSets/v3.50~22/PowerGauge.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/TileRelay.qml b/FileSets/v3.50~22/TileRelay.qml
deleted file mode 120000
index 5f86edbf..00000000
--- a/FileSets/v3.50~22/TileRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~22/dbus_digitalinputs.py b/FileSets/v3.50~22/dbus_digitalinputs.py
deleted file mode 100755
index a49e9c6e..00000000
--- a/FileSets/v3.50~22/dbus_digitalinputs.py
+++ /dev/null
@@ -1,651 +0,0 @@
-#!/usr/bin/python3 -u
-
-#### modified for ExtTransferSwitch package
-
-import sys, os
-import signal
-from threading import Thread
-from select import select, epoll, EPOLLPRI
-from functools import partial
-from collections import namedtuple
-from argparse import ArgumentParser
-import traceback
-sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python'))
-
-from dbus.mainloop.glib import DBusGMainLoop
-import dbus
-from gi.repository import GLib
-from vedbus import VeDbusService, VeDbusItemImport
-from settingsdevice import SettingsDevice
-
-VERSION = '0.23'
-MAXCOUNT = 2**31-1
-SAVEINTERVAL = 60000
-
-INPUT_FUNCTION_COUNTER = 1
-INPUT_FUNCTION_INPUT = 2
-
-Translation = namedtuple('Translation', ['no', 'yes'])
-
-# Only append at the end
-INPUTTYPES = [
- 'Disabled',
- 'Pulse meter',
- 'Door',
- 'Bilge pump',
- 'Bilge alarm',
- 'Burglar alarm',
- 'Smoke alarm',
- 'Fire alarm',
- 'CO2 alarm',
- 'Generator',
- 'Generic I/O',
- 'Touch enable',
-#### added for ExtTransferSwitch package -- must be LAST in the list
- 'Transfer switch'
-]
-
-# Translations. The text will be used only for GetText, it will be translated
-# in the gui.
-TRANSLATIONS = [
- Translation('low', 'high'),
- Translation('off', 'on'),
- Translation('no', 'yes'),
- Translation('open', 'closed'),
- Translation('ok', 'alarm'),
- Translation('running', 'stopped'),
-#### added for ExtTransferSwitch package
- Translation('on generator', 'on grid')
-]
-
-class SystemBus(dbus.bus.BusConnection):
- def __new__(cls):
- return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM)
-
-class SessionBus(dbus.bus.BusConnection):
- def __new__(cls):
- return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
-
-class BasePulseCounter(object):
- pass
-
-class DebugPulseCounter(BasePulseCounter):
- def __init__(self):
- self.gpiomap = {}
-
- def register(self, path, gpio):
- self.gpiomap[gpio] = None
- return 0
-
- def unregister(self, gpio):
- del self.gpiomap[gpio]
-
- def registered(self, gpio):
- return gpio in self.gpiomap
-
- def __call__(self):
- from itertools import cycle
- from time import sleep
- for level in cycle([0, 1]):
- for gpio in list(self.gpiomap.keys()):
- yield gpio, level
- sleep(0.25/len(self.gpiomap))
-
-class EpollPulseCounter(BasePulseCounter):
- def __init__(self):
- self.gpiomap = {}
- self.states = {}
- self.ob = epoll()
-
- def register(self, path, gpio):
- path = os.path.realpath(path)
-
- # Set up gpio for rising edge interrupts
- with open(os.path.join(path, 'edge'), 'ab') as fp:
- fp.write(b'both')
-
- fp = open(os.path.join(path, 'value'), 'rb')
- level = int(fp.read()) # flush it in case it's high at startup
- self.gpiomap[gpio] = fp
- self.states[gpio] = level
- self.ob.register(fp, EPOLLPRI)
- return level
-
- def unregister(self, gpio):
- fp = self.gpiomap[gpio]
- self.ob.unregister(fp)
- del self.gpiomap[gpio]
- del self.states[gpio]
- fp.close()
-
- def registered(self, gpio):
- return gpio in self.gpiomap
-
- def __call__(self):
- while True:
- # We have a timeout of 1 second on the poll, because poll() only
- # looks at files in the epoll object at the time poll() was called.
- # The timeout means we let other files (added via calls to
- # register/unregister) into the loop at least that often.
- self.ob.poll(1)
-
- # When coming out of the epoll call, we read all the gpios to make
- # sure we didn't miss any edges. This is a safety fallback that
- # ensures everything is up to date once a second, but
- # edge-triggered results are handled immediately.
- # NOTE: There has not been a report of a missed interrupt yet.
- # Belts and suspenders.
- for gpio, fp in list(self.gpiomap.items()):
- os.lseek(fp.fileno(), 0, os.SEEK_SET)
- v = int(os.read(fp.fileno(), 1))
- if v != self.states[gpio]:
- self.states[gpio] = v
- yield gpio, v
-
-class PollingPulseCounter(BasePulseCounter):
- def __init__(self):
- self.gpiomap = {}
-
- def register(self, path, gpio):
- path = os.path.realpath(path)
-
- fp = open(os.path.join(path, 'value'), 'rb')
- level = int(fp.read())
- self.gpiomap[gpio] = [fp, level]
- return level
-
- def unregister(self, gpio):
- del self.gpiomap[gpio]
-
- def registered(self, gpio):
- return gpio in self.gpiomap
-
- def __call__(self):
- from itertools import cycle
- from time import sleep
- while True:
- for gpio, (fp, level) in list(self.gpiomap.items()):
- fp.seek(0, os.SEEK_SET)
- v = int(fp.read())
- if v != level:
- self.gpiomap[gpio][1] = v
- yield gpio, v
- sleep(1)
-
-class HandlerMaker(type):
- """ Meta-class for keeping track of all extended classes. """
- def __init__(cls, name, bases, attrs):
- if not hasattr(cls, 'handlers'):
- cls.handlers = {}
- else:
- cls.handlers[cls.type_id] = cls
-
-class PinHandler(object, metaclass=HandlerMaker):
- product_id = 0xFFFF
- _product_name = 'Generic GPIO'
- dbus_name = "digital"
- def __init__(self, bus, base, path, gpio, settings):
- self.gpio = gpio
- self.path = path
- self.bus = bus
- self.settings = settings
- self._level = 0 # Remember last state
-
- self.service = VeDbusService(
- "{}.{}.input{:02d}".format(base, self.dbus_name, gpio), bus=bus)
-
- # Add objects required by ve-api
- self.service.add_path('/Mgmt/ProcessName', __file__)
- self.service.add_path('/Mgmt/ProcessVersion', VERSION)
- self.service.add_path('/Mgmt/Connection', path)
- self.service.add_path('/DeviceInstance', gpio)
- self.service.add_path('/ProductId', self.product_id)
- self.service.add_path('/ProductName', self.product_name)
- self.service.add_path('/Connected', 1)
-
- # Custom name setting
- def _change_name(p, v):
- # This should fire a change event that will update product_name
- # below.
- settings['name'] = v
- return True
-
- self.service.add_path('/CustomName', settings['name'], writeable=True,
- onchangecallback=_change_name)
-
- # We'll count the pulses for all types of services
- self.service.add_path('/Count', value=settings['count'])
-
- @property
- def product_name(self):
- return self.settings['name'] or self._product_name
-
- @product_name.setter
- def product_name(self, v):
- # Some pin types don't have an associated service (Disabled pins for
- # example)
- if self.service is not None:
- self.service['/ProductName'] = v or self._product_name
-
- def deactivate(self):
- self.save_count()
- self.service.__del__()
- del self.service
- self.service = None
-
- @property
- def level(self):
- return self._level
-
- @level.setter
- def level(self, l):
- self._level = int(bool(l))
-
- def toggle(self, level):
- raise NotImplementedError
-
- def _toggle(self, level, service):
- # Only increment Count on rising edge.
- if level and level != self._level:
- service['/Count'] = (service['/Count']+1) % MAXCOUNT
- self._level = level
-
- def refresh(self):
- """ Toggle state to last remembered state. This is called if settings
- are changed so the Service can recalculate paths. """
- self.toggle(self._level)
-
- def save_count(self):
- if self.service is not None:
- self.settings['count'] = self.count
-
- @property
- def active(self):
- return self.service is not None
-
- @property
- def count(self):
- return self.service['/Count']
-
- @count.setter
- def count(self, v):
- self.service['/Count'] = v
-
- @classmethod
- def createHandler(cls, _type, *args, **kwargs):
- if _type in cls.handlers:
- return cls.handlers[_type](*args, **kwargs)
- return None
-
-
-class NopPin(object):
- """ Mixin for a pin with empty behaviour. Mix in BEFORE PinHandler so that
- __init__ overrides the base behaviour. """
- def __init__(self, bus, base, path, gpio, settings):
- self.service = None
- self.bus = bus
- self.settings = settings
- self._level = 0 # Remember last state
-
- def deactivate(self):
- pass
-
- def toggle(self, level):
- self._level = level
-
- def save_count(self):
- # Do nothing
- pass
-
- @property
- def count(self):
- return self.settings['count']
-
- @count.setter
- def count(self, v):
- pass
-
- def refresh(self):
- pass
-
-
-class DisabledPin(NopPin, PinHandler):
- """ Place holder for a disabled pin. """
- _product_name = 'Disabled'
- type_id = 0
-
-
-class VolumeCounter(PinHandler):
- product_id = 0xA165
- _product_name = "Generic pulse meter"
- dbus_name = "pulsemeter"
- type_id = 1
-
- def __init__(self, bus, base, path, gpio, settings):
- super(VolumeCounter, self).__init__(bus, base, path, gpio, settings)
- self.service.add_path('/Aggregate', value=self.count*self.rate,
- gettextcallback=lambda p, v: (str(v) + ' cubic meter'))
-
- @property
- def rate(self):
- return self.settings['rate']
-
- def toggle(self, level):
- with self.service as s:
- super(VolumeCounter, self)._toggle(level, s)
- s['/Aggregate'] = self.count * self.rate
-
-class TouchEnable(NopPin, PinHandler):
- """ The pin is used to enable/disable the Touch screen when toggled.
- No dbus-service is created. """
- _product_name = 'TouchEnable'
- type_id = 11
-
- def __init__(self, *args, **kwargs):
- super(TouchEnable, self).__init__(*args, **kwargs)
- self.item = VeDbusItemImport(self.bus,
- "com.victronenergy.settings", "/Settings/Gui/TouchEnabled")
-
- def toggle(self, level):
- super(TouchEnable, self).toggle(level)
-
- # Toggle the touch-enable setting on the downward edge.
- # Level is expected to be high with the switch open, and
- # pulled low when pushed.
- if level == 0:
- enabled = bool(self.item.get_value())
- self.item.set_value(int(not enabled))
-
- def deactivate(self):
- # Always re-enable touch when the pin is deactivated.
- # This adds another layer of protection against accidental
- # lockout.
- self.item.set_value(1)
- del self.item
-
-class PinAlarm(PinHandler):
- product_id = 0xA166
- _product_name = "Generic digital input"
- dbus_name = "digitalinput"
- type_id = 0xFF
- translation = 0 # low, high
-
- def __init__(self, bus, base, path, gpio, settings):
- super(PinAlarm, self).__init__(bus, base, path, gpio, settings)
- self.service.add_path('/InputState', value=0)
- self.service.add_path('/State', value=self.get_state(0),
- gettextcallback=lambda p, v: TRANSLATIONS[v//2][v%2])
- self.service.add_path('/Alarm', value=self.get_alarm_state(0))
-
- # Also expose the type
- self.service.add_path('/Type', value=self.type_id,
- gettextcallback=lambda p, v: INPUTTYPES[v])
-
- def toggle(self, level):
- with self.service as s:
- super(PinAlarm, self)._toggle(level, s)
- s['/InputState'] = bool(level)*1
- s['/State'] = self.get_state(level)
- # Ensure that the alarm flag resets if the /AlarmSetting config option
- # disappears.
- s['/Alarm'] = self.get_alarm_state(level)
-
- def get_state(self, level):
- state = level ^ self.settings['invert']
- return 2 * self.translation + state
-
- def get_alarm_state(self, level):
- return 2 * bool(
- (level ^ self.settings['invertalarm']) and self.settings['alarm'])
-
-
-class Generator(PinAlarm):
- _product_name = "Generator"
- type_id = 9
- translation = 5 # running, stopped
-
- def __init__(self, *args, **kwargs):
- super(Generator, self).__init__(*args, **kwargs)
- # Periodically rewrite the generator selection. The Multi may reset
- # causing this to be lost, or a race condition on startup may cause
- # it to not be set properly.
- self._timer = GLib.timeout_add(30000,
- lambda: self.select_generator(self.level ^ self.settings['invert'] ^ 1) or True)
-
-#### added for ExtTransferSwitch package
- self.mainVeBusServiceItem = None
-#### end added for ExtTransferSwitch package
-
-
- def select_generator(self, v):
-
- # Find all vebus services, and let them know
- try:
- services = [n for n in self.bus.list_names() if n.startswith(
- 'com.victronenergy.vebus.')]
- for n in services:
-#### added for ExtTransferSwitch package
- # skip this service if it is the main VE.Bus device
- # processing for that is handled in ExtTransferSwitch
- try:
- if self.mainVeBusServiceItem == None:
- self.mainVeBusServiceItem = VeDbusItemImport(self.bus,
- "com.victronenergy.service", "/VebusService")
- if n == self.mainVeBusService.get_value ():
- continue
- except:
- pass
-#### end added for ExtTransferSwitch package
-
- self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', None,
- 'SetValue', 'v', [v], None, None)
- except dbus.exceptions.DBusException:
- print ("DBus exception setting RemoteGeneratorSelected")
- traceback.print_exc()
-
- def toggle(self, level):
- super(Generator, self).toggle(level)
-
- # Follow the same inversion sense as for display
- self.select_generator(level ^ self.settings['invert'] ^ 1)
-
- def deactivate(self):
- super(Generator, self).deactivate()
- # When deactivating, reset the generator selection state
- self.select_generator(0)
-
- # And kill the periodic job
- if self._timer is not None:
- GLib.source_remove(self._timer)
- self._timer = None
-
-# Various types of things we might want to monitor
-class DoorSensor(PinAlarm):
- _product_name = "Door alarm"
- type_id = 2
- translation = 3 # open, closed
-
-class BilgePump(PinAlarm):
- _product_name = "Bilge pump"
- type_id = 3
- translation = 1 # off, on
-
-class BilgeAlarm(PinAlarm):
- _product_name = "Bilge alarm"
- type_id = 4
- translation = 4 # ok, alarm
-
-class BurglarAlarm(PinAlarm):
- _product_name = "Burglar alarm"
- type_id = 5
- translation = 4 # ok, alarm
-
-class SmokeAlarm(PinAlarm):
- _product_name = "Smoke alarm"
- type_id = 6
- translation = 4 # ok, alarm
-
-class FireAlarm(PinAlarm):
- _product_name = "Fire alarm"
- type_id = 7
- translation = 4 # ok, alarm
-
-class CO2Alarm(PinAlarm):
- _product_name = "CO2 alarm"
- type_id = 8
- translation = 4 # ok, alarm
-
-class GenericIO(PinAlarm):
- _product_name = "Generic I/O"
- type_id = 10
- translation = 0 # low, high
-
-#### added for ExtTransferSwitch package
-class TransferSwitch(PinAlarm):
- _product_name = "External AC Input transfer switch"
- type_id = 12
- translation = 6 # Grid In / Generator In
-
-
-def dbusconnection():
- return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
-
-
-def main():
- parser = ArgumentParser(description=sys.argv[0])
- parser.add_argument('--servicebase',
- help='Base service name on dbus, default is com.victronenergy',
- default='com.victronenergy')
- parser.add_argument('--poll',
- help='Use a different kind of polling. Options are epoll, dumb and debug',
- default='epoll')
- parser.add_argument('inputs', nargs='+', help='Path to digital input')
- args = parser.parse_args()
-
- PulseCounter = {
- 'debug': DebugPulseCounter,
- 'poll': PollingPulseCounter,
- }.get(args.poll, EpollPulseCounter)
-
- DBusGMainLoop(set_as_default=True)
-
- # Keep track of enabled services
- services = {}
- inputs = dict(enumerate(args.inputs, 1))
- pulses = PulseCounter() # callable that iterates over pulses
-
- def register_gpio(path, gpio, bus, settings):
- _type = settings['inputtype']
- print ("Registering GPIO {} for type {}".format(gpio, _type))
-
- handler = PinHandler.createHandler(_type,
- bus, args.servicebase, path, gpio, settings)
- services[gpio] = handler
-
- # Only monitor if enabled
- if _type > 0:
- handler.level = pulses.register(path, gpio)
- handler.refresh()
-
- def unregister_gpio(gpio):
- print ("unRegistering GPIO {}".format(gpio))
- pulses.unregister(gpio)
- services[gpio].deactivate()
-
- def handle_setting_change(inp, setting, old, new):
- # This handler may also be called if some attribute of a setting
- # is changed, but not the value. Bail if the value is unchanged.
- if old == new:
- return
-
- if setting == 'inputtype':
- if new:
- # Get current bus and settings objects, to be reused
- service = services[inp]
- bus, settings = service.bus, service.settings
-
- # Input enabled. If already enabled, unregister the old one first.
- if pulses.registered(inp):
- unregister_gpio(inp)
-
- # Before registering the new input, reset its settings to defaults
- settings['count'] = 0
- settings['invert'] = 0
- settings['invertalarm'] = 0
- settings['alarm'] = 0
-
- # Register it
- register_gpio(inputs[inp], inp, bus, settings)
- elif old:
- # Input disabled
- unregister_gpio(inp)
- elif setting in ('rate', 'invert', 'alarm', 'invertalarm'):
- services[inp].refresh()
- elif setting == 'name':
- services[inp].product_name = new
- elif setting == 'count':
- # Don't want this triggered on a period save, so only execute
- # if it has changed.
- v = int(new)
- s = services[inp]
- if s.count != v:
- s.count = v
- s.refresh()
-
- for inp, pth in inputs.items():
- supported_settings = {
- 'inputtype': ['/Settings/DigitalInput/{}/Type'.format(inp), 0, 0, len(INPUTTYPES)-1],
- 'rate': ['/Settings/DigitalInput/{}/Multiplier'.format(inp), 0.001, 0, 1.0],
- 'count': ['/Settings/DigitalInput/{}/Count'.format(inp), 0, 0, MAXCOUNT, 1],
- 'invert': ['/Settings/DigitalInput/{}/InvertTranslation'.format(inp), 0, 0, 1],
- 'invertalarm': ['/Settings/DigitalInput/{}/InvertAlarm'.format(inp), 0, 0, 1],
- 'alarm': ['/Settings/DigitalInput/{}/AlarmSetting'.format(inp), 0, 0, 1],
- 'name': ['/Settings/DigitalInput/{}/CustomName'.format(inp), '', '', ''],
- }
- bus = dbusconnection()
- sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, inp), timeout=10)
- register_gpio(pth, inp, bus, sd)
-
- def poll(mainloop):
- from time import time
- idx = 0
-
- try:
- for inp, level in pulses():
- # epoll object only resyncs once a second. We may receive
- # a pulse for something that's been deregistered.
- try:
- services[inp].toggle(level)
- except KeyError:
- continue
- except:
- traceback.print_exc()
- mainloop.quit()
-
- # Need to run the gpio polling in separate thread. Pass in the mainloop so
- # the thread can kill us if there is an exception.
- mainloop = GLib.MainLoop()
-
- poller = Thread(target=lambda: poll(mainloop))
- poller.daemon = True
- poller.start()
-
- # Periodically save the counter
- def save_counters():
- for inp in inputs:
- services[inp].save_count()
- return True
- GLib.timeout_add(SAVEINTERVAL, save_counters)
-
- # Save counter on shutdown
- signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
-
- try:
- mainloop.run()
- except KeyboardInterrupt:
- pass
- finally:
- save_counters()
-
-if __name__ == "__main__":
- main()
diff --git a/FileSets/v3.50~22/startstop.py b/FileSets/v3.50~22/startstop.py
deleted file mode 100644
index ad4e1d9a..00000000
--- a/FileSets/v3.50~22/startstop.py
+++ /dev/null
@@ -1,1567 +0,0 @@
-#!/usr/bin/python -u
-# -*- coding: utf-8 -*-
-
-#### GuiMods
-#### This file has been modified to allow the generator running state derived from the generator digital input
-#### or the genset AC input
-#### If the incoming generator state changes, the manual start state is updated
-#### time accumulation is suspended when the generator is not running
-#### A switch in the generator settings menucontrols whethter the incoming state affects manual start or time accumulaiton
-#### It is now possible to start the generator manually and have it stop automatically based on the preset conditions
-#### for automaitc start / stop
-#### A service interval timer was added so the accumulated run time does not need to be reset,
-#### providing total run time for the generator
-#### warm-up and cool-down periods have been modified in order to work well with an external transfer switch
-#### selecting grid or generator ahead of a MultiPlus input.
-#### Search for #### GuiMods to find changes
-
-# Function
-# dbus_generator monitors the dbus for batteries (com.victronenergy.battery.*) and
-# vebus com.victronenergy.vebus.*
-# Battery and vebus monitors can be configured through the gui.
-# It then monitors SOC, AC loads, battery current and battery voltage,to auto start/stop the generator based
-# on the configuration settings. Generator can be started manually or periodically setting a tes trun period.
-# Time zones function allows to use different values for the conditions along the day depending on time
-
-import dbus
-import datetime
-import calendar
-import time
-import sys
-import json
-import os
-import logging
-from collections import OrderedDict
-import monotonic_time
-from gen_utils import SettingsPrefix, Errors, States, enum
-from gen_utils import create_dbus_service
-# Victron packages
-sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'ext', 'velib_python'))
-from ve_utils import exit_on_error
-from settingsdevice import SettingsDevice
-
-RunningConditions = enum(
- Stopped = 0,
- Manual = 1,
- TestRun = 2,
- LossOfCommunication = 3,
- Soc = 4,
- Acload = 5,
- BatteryCurrent = 6,
- BatteryVoltage = 7,
- InverterHighTemp = 8,
- InverterOverload = 9,
- StopOnAc1 = 10,
- StopOnAc2 = 11)
-
-Capabilities = enum(
- WarmupCooldown = 1
-)
-
-SYSTEM_SERVICE = 'com.victronenergy.system'
-BATTERY_PREFIX = '/Dc/Battery'
-HISTORY_DAYS = 30
-AUTOSTART_DISABLED_ALARM_TIME = 600
-
-def safe_max(args):
- try:
- return max(x for x in args if x is not None)
- except ValueError:
- return None
-
-class Condition(object):
- def __init__(self, parent):
- self.parent = parent
- self.reached = False
- self.start_timer = 0
- self.stop_timer = 0
- self.valid = True
- self.enabled = False
- self.retries = 0
-
- def __getitem__(self, key):
- try:
- return getattr(self, key)
- except AttributeError:
- raise KeyError(key)
-
- def __setitem__(self, key, value):
- setattr(self, key, value)
-
- def get_value(self):
- raise NotImplementedError("get_value")
-
- @property
- def vebus_service(self):
- return self.parent._vebusservice if self.parent._vebusservice else ''
-
- @property
- def monitor(self):
- return self.parent._dbusmonitor
-
-class SocCondition(Condition):
- name = 'soc'
- monitoring = 'battery'
- boolean = False
- timed = True
-
- def get_value(self):
- return self.parent._get_battery().soc
-
-class AcLoadCondition(Condition):
- name = 'acload'
- monitoring = 'vebus'
- boolean = False
- timed = True
-
- def get_value(self):
- loadOnAcOut = []
- totalConsumption = []
-
- for phase in ['L1', 'L2', 'L3']:
- # Get the values directly from the inverter, systemcalc doesn't provide raw inverted power
- loadOnAcOut.append(self.monitor.get_value(self.vebus_service, ('/Ac/Out/%s/P' % phase)))
-
- # Calculate total consumption, '/Ac/Consumption/%s/Power' is deprecated
- c_i = self.monitor.get_value(SYSTEM_SERVICE, ('/Ac/ConsumptionOnInput/%s/Power' % phase))
- c_o = self.monitor.get_value(SYSTEM_SERVICE, ('/Ac/ConsumptionOnOutput/%s/Power' % phase))
- totalConsumption.append(sum(filter(None, (c_i, c_o))))
-
- # Invalidate if vebus is not available
- if loadOnAcOut[0] == None:
- return None
-
- # Total consumption
- if self.parent._settings['acloadmeasurement'] == 0:
- return sum(filter(None, totalConsumption))
-
- # Load on inverter AC out
- if self.parent._settings['acloadmeasurement'] == 1:
- return sum(filter(None, loadOnAcOut))
-
- # Highest phase load
- if self.parent._settings['acloadmeasurement'] == 2:
- return safe_max(loadOnAcOut)
-
-class BatteryCurrentCondition(Condition):
- name = 'batterycurrent'
- monitoring = 'battery'
- boolean = False
- timed = True
-
- def get_value(self):
- c = self.parent._get_battery().current
- if c is not None:
- c *= -1
- return c
-
-class BatteryVoltageCondition(Condition):
- name = 'batteryvoltage'
- monitoring = 'battery'
- boolean = False
- timed = True
-
- def get_value(self):
- return self.parent._get_battery().voltage
-
-class InverterTempCondition(Condition):
- name = 'inverterhightemp'
- monitoring = 'vebus'
- boolean = True
- timed = True
-
- def get_value(self):
- v = self.monitor.get_value(self.vebus_service,
- '/Alarms/HighTemperature')
-
- # When multi is connected to CAN-bus, alarms are published to
- # /Alarms/HighTemperature... but when connected to vebus alarms are
- # splitted in three phases and published to /Alarms/LX/HighTemperature...
- if v is None:
- inverterHighTemp = []
- for phase in ['L1', 'L2', 'L3']:
- # Inverter alarms must be fetched directly from the inverter service
- inverterHighTemp.append(self.monitor.get_value(self.vebus_service, ('/Alarms/%s/HighTemperature' % phase)))
- return safe_max(inverterHighTemp)
- return v
-
-class InverterOverloadCondition(Condition):
- name = 'inverteroverload'
- monitoring = 'vebus'
- boolean = True
- timed = True
-
- def get_value(self):
- v = self.monitor.get_value(self.vebus_service,
- '/Alarms/Overload')
-
- # When multi is connected to CAN-bus, alarms are published to
- # /Alarms/Overload... but when connected to vebus alarms are
- # splitted in three phases and published to /Alarms/LX/Overload...
- if v is None:
- inverterOverload = []
- for phase in ['L1', 'L2', 'L3']:
- # Inverter alarms must be fetched directly from the inverter service
- inverterOverload.append(self.monitor.get_value(self.vebus_service, ('/Alarms/%s/Overload' % phase)))
- return safe_max(inverterOverload)
- return v
-
-class StopOnAc1Condition(Condition):
- name = 'stoponac1'
- monitoring = 'vebus'
- boolean = True
- timed = False
-
- def get_value(self):
- # AC input 1
- available = self.monitor.get_value(self.vebus_service,
- '/Ac/State/AcIn1Available')
- if available is None:
- # Not supported in firmware, fall back to old behaviour
- activein = self.monitor.get_value(self.vebus_service,
- '/Ac/ActiveIn/ActiveInput')
-
- # Active input is connected
- connected = self.monitor.get_value(self.vebus_service,
- '/Ac/ActiveIn/Connected')
- if None not in (activein, connected):
- return activein == 0 and connected == 1
- return None
-
- return bool(available)
-
-class StopOnAc2Condition(Condition):
- name = 'stoponac2'
- monitoring = 'vebus'
- boolean = True
- timed = False
-
- def get_value(self):
- # AC input 2 available (used when grid is on AC-in-2)
- available = self.monitor.get_value(self.vebus_service,
- '/Ac/State/AcIn2Available')
-
- return None if available is None else bool(available)
-
-class Battery(object):
- def __init__(self, monitor, service, prefix):
- self.monitor = monitor
- self.service = service
- self.prefix = prefix
-
- @property
- def voltage(self):
- return self.monitor.get_value(self.service, self.prefix + '/Voltage')
-
- @property
- def current(self):
- return self.monitor.get_value(self.service, self.prefix + '/Current')
-
- @property
- def soc(self):
- # Soc from the device doesn't have the '/Dc/0' prefix like the current and voltage do, but it does
- # have the same prefix on systemcalc
- return self.monitor.get_value(self.service, (BATTERY_PREFIX if self.prefix == BATTERY_PREFIX else '') + '/Soc')
-
-class StartStop(object):
- _driver = None
- def __init__(self, instance):
-#### GuiMods
- logging.info ("GuiMods version of startstop.py")
- self._currentTime = 0
- self._last_update_mtime = 0
- self._accumulatedRunTime = 0
- self._digitalInputTypeObject = None
- self._generatorInputStateObject = 0
- self._lastState = 0
- self._externalOverride = False
- self._externalOverrideDelay = 99
- self._lastExternalOverride = False
- self._searchDelay = 99
- self._linkToExternalState = False
-#### GuiMods warm-up / cool-down
- self._warmUpEndTime = 0
- self._coolDownEndTime = 0
- self._ac1isIgnored = False
- self._ac2isIgnored = False
- self._activeAcInIsIgnored = False
- self._acInIsGenerator = False
-
- self._dbusservice = None
- self._settings = None
- self._dbusmonitor = None
- self._remoteservice = None
- self._name = None
- self._enabled = False
- self._instance = instance
-
- # One second per retry
- self.RETRIES_ON_ERROR = 300
- self._testrun_soc_retries = 0
- self._last_counters_check = 0
-
- self._starttime = 0
- self._stoptime = 0 # Used for cooldown
- self._manualstarttimer = 0
- self._last_runtime_update = 0
- self._timer_runnning = 0
-
- # The installer left autostart disabled
- self._autostart_last_time = self._get_monotonic_seconds()
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
-
-
- # Manual battery service selection is deprecated in favour
- # of getting the values directly from systemcalc, we keep
- # manual selected services handling for compatibility reasons.
- self._vebusservice = None
- self._errorstate = 0
- self._battery_service = None
- self._battery_prefix = None
-
- self._acpower_inverter_input = {
- 'timeout': 0,
- 'unabletostart': False
- }
-
- # Order is important. Conditions are evaluated in the order listed.
- self._condition_stack = OrderedDict({
- SocCondition.name: SocCondition(self),
- AcLoadCondition.name: AcLoadCondition(self),
- BatteryCurrentCondition.name: BatteryCurrentCondition(self),
- BatteryVoltageCondition.name: BatteryVoltageCondition(self),
- InverterTempCondition.name: InverterTempCondition(self),
- InverterOverloadCondition.name: InverterOverloadCondition(self),
- StopOnAc1Condition.name: StopOnAc1Condition(self),
- StopOnAc2Condition.name: StopOnAc2Condition(self)
- })
-
- def set_sources(self, dbusmonitor, settings, name, remoteservice):
- self._settings = SettingsPrefix(settings, name)
- self._dbusmonitor = dbusmonitor
- self._remoteservice = remoteservice
- self._name = name
-
- self.log_info('Start/stop instance created for %s.' % self._remoteservice)
- self._remote_setup()
-
- def _create_service(self):
- self._dbusservice = self._create_dbus_service()
-
- # The driver used for this start/stop service
- self._dbusservice.add_path('/Type', value=self._driver)
- # State: None = invalid, 0 = stopped, 1 = running, 2=Warm-up, 3=Cool-down
- self._dbusservice.add_path('/State', value=None, gettextcallback=lambda p, v: States.get_description(v))
- # RunningByConditionCode: Numeric Companion to /RunningByCondition below, but
- # also encompassing a Stopped state.
- self._dbusservice.add_path('/RunningByConditionCode', value=None)
- # Error
- self._dbusservice.add_path('/Error', value=None, gettextcallback=lambda p, v: Errors.get_description(v))
- # Condition that made the generator start
- self._dbusservice.add_path('/RunningByCondition', value=None)
- # Runtime
- self._dbusservice.add_path('/Runtime', value=None, gettextcallback=self._seconds_to_text)
- # Today runtime
- self._dbusservice.add_path('/TodayRuntime', value=None, gettextcallback=self._seconds_to_text)
- # Test run runtime
- self._dbusservice.add_path('/TestRunIntervalRuntime', value=None , gettextcallback=self._seconds_to_text)
- # Next test run date, values is 0 for test run disabled
- self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=lambda p, v: datetime.datetime.fromtimestamp(v).strftime('%c'))
- # Next test run is needed 1, not needed 0
- self._dbusservice.add_path('/SkipTestRun', value=None)
- # Manual start
- self._dbusservice.add_path('/ManualStart', value=None, writeable=True)
- # Manual start timer
- self._dbusservice.add_path('/ManualStartTimer', value=None, writeable=True)
- # Silent mode active
- self._dbusservice.add_path('/QuietHours', value=None)
- # Alarms
- self._dbusservice.add_path('/Alarms/NoGeneratorAtAcIn', value=None)
- self._dbusservice.add_path('/Alarms/ServiceIntervalExceeded', value=None)
- self._dbusservice.add_path('/Alarms/AutoStartDisabled', value=None)
- self._dbusservice.add_path('/Alarms/RemoteStartModeDisabled', value=None)
- # Autostart
- self._dbusservice.add_path('/AutoStartEnabled', value=None, writeable=True, onchangecallback=self._set_autostart)
- # Accumulated runtime
- self._dbusservice.add_path('/AccumulatedRuntime', value=None)
- # Service interval
- self._dbusservice.add_path('/ServiceInterval', value=None)
- # Capabilities, where we can add bits
- self._dbusservice.add_path('/Capabilities', value=0)
- # Service countdown, calculated by running time and service interval
- self._dbusservice.add_path('/ServiceCounter', value=None)
- self._dbusservice.add_path('/ServiceCounterReset', value=None, writeable=True, onchangecallback=self._reset_service_counter)
- # Publish what service we're controlling, and the productid
- self._dbusservice.add_path('/GensetService', value=self._remoteservice)
- self._dbusservice.add_path('/GensetInstance',
- value=self._dbusmonitor.get_value(self._remoteservice, '/DeviceInstance'))
- self._dbusservice.add_path('/GensetProductId',
- value=self._dbusmonitor.get_value(self._remoteservice, '/ProductId'))
-
- self._dbusservice.register()
- # We need to set the values after creating the paths to trigger the 'onValueChanged' event for the gui
- # otherwise the gui will report the paths as invalid if we remove and recreate the paths without
- # restarting the dbusservice.
- self._dbusservice['/State'] = 0
- self._dbusservice['/RunningByConditionCode'] = RunningConditions.Stopped
- self._dbusservice['/Error'] = 0
- self._dbusservice['/RunningByCondition'] = ''
- self._dbusservice['/Runtime'] = 0
- self._dbusservice['/TodayRuntime'] = 0
- self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(self._settings['testruninterval'])
- self._dbusservice['/NextTestRun'] = None
- self._dbusservice['/SkipTestRun'] = None
- self._dbusservice['/ProductName'] = "Generator start/stop"
- self._dbusservice['/ManualStart'] = 0
- self._dbusservice['/ManualStartTimer'] = 0
- self._dbusservice['/QuietHours'] = 0
- self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 0
- self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 0
- self._dbusservice['/Alarms/AutoStartDisabled'] = 0 # GX auto start/stop
- self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0 # Genset remote start mode
- self._dbusservice['/AutoStartEnabled'] = self._settings['autostart']
- self._dbusservice['/AccumulatedRuntime'] = int(self._settings['accumulatedtotal'])
- self._dbusservice['/ServiceInterval'] = int(self._settings['serviceinterval'])
- self._dbusservice['/ServiceCounter'] = None
- self._dbusservice['/ServiceCounterReset'] = 0
-
-#### GuiMods
- # generator input running state
- self._dbusservice.add_path('/GeneratorRunningState', value=None)
- # external override active
- self._dbusservice.add_path('/ExternalOverride', value=None)
- self._dbusservice['/GeneratorRunningState'] = "?"
- self._dbusservice['/ExternalOverride'] = False
- self._ignoreAutoStartCondition = False
-
-
- @property
- def capabilities(self):
- return self._dbusservice['/Capabilities']
-
- def _set_autostart(self, path, value):
- if 0 <= value <= 1:
- self._settings['autostart'] = int(value)
- return True
- return False
-
- def enable(self):
- if self._enabled:
- return
- self.log_info('Enabling auto start/stop and taking control of remote switch')
- self._create_service()
- self._determineservices()
- self._update_remote_switch()
- # If cooldown or warmup is enabled, the Quattro may be left in a bad
- # state if there is an unfortunate crash or a reboot. Set the ignore_ac
- # flag to a sane value on startup.
- if self._settings['cooldowntime'] > 0 or \
- self._settings['warmuptime'] > 0:
- self._set_ignore_ac(False) ###########
- self._enabled = True
-
- def disable(self):
- if not self._enabled:
- return
- self.log_info('Disabling auto start/stop, releasing control of remote switch')
- self._remove_service()
- self._enabled = False
-
- def remove(self):
- self.disable()
- self.log_info('Removed from start/stop instances')
-
- def _remove_service(self):
- self._dbusservice.__del__()
- self._dbusservice = None
-
- def device_added(self, dbusservicename, instance):
- self._determineservices()
-
- def device_removed(self, dbusservicename, instance):
- self._determineservices()
-
- def get_error(self):
- return self._dbusservice['/Error']
-
- def set_error(self, errorn):
- self._dbusservice['/Error'] = errorn
-
- def clear_error(self):
- self._dbusservice['/Error'] = Errors.NONE
-
- def dbus_value_changed(self, dbusServiceName, dbusPath, options, changes, deviceInstance):
- if self._dbusservice is None:
- return
-
- # AcIn1Available is needed to determine capabilities, but may
- # only show up later. So we have to wait for it here.
- if self._vebusservice is not None and \
- dbusServiceName == self._vebusservice and \
- dbusPath == '/Ac/State/AcIn1Available':
- self._set_capabilities()
-
- if dbusServiceName != 'com.victronenergy.system':
- return
- if dbusPath == '/AutoSelectedBatteryMeasurement' and self._settings['batterymeasurement'] == 'default':
- self._determineservices()
-
- if dbusPath == '/VebusService':
- self._determineservices()
-
- def handlechangedsetting(self, setting, oldvalue, newvalue):
- if self._dbusservice is None:
- return
- if self._name not in setting:
- # Not our setting
- return
-
- s = self._settings.removeprefix(setting)
-
- if s == 'batterymeasurement':
- self._determineservices()
- # Reset retries and valid if service changes
- for condition in self._condition_stack.values():
- if condition['monitoring'] == 'battery':
- condition['valid'] = True
- condition['retries'] = 0
-
- if s == 'autostart':
- self.log_info('Autostart function %s.' % ('enabled' if newvalue == 1 else 'disabled'))
- self._dbusservice['/AutoStartEnabled'] = self._settings['autostart']
-
- if self._dbusservice is not None and s == 'testruninterval':
- self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(
- self._settings['testruninterval'])
-
- if s == 'serviceinterval':
- try:
- self._dbusservice['/ServiceInterval'] = int(newvalue)
- except TypeError:
- pass
- if newvalue == 0:
- self._dbusservice['/ServiceCounter'] = None
- else:
- self._update_accumulated_time()
- if s == 'lastservicereset':
- self._update_accumulated_time()
-
- def _reset_service_counter(self, path, value):
- if (path == '/ServiceCounterReset' and value == int(1) and self._dbusservice['/AccumulatedRuntime']):
- self._settings['lastservicereset'] = self._dbusservice['/AccumulatedRuntime']
- self._update_accumulated_time()
- self.log_info('Service counter reset triggered.')
-
- return True
-
- def _seconds_to_text(self, path, value):
- m, s = divmod(value, 60)
- h, m = divmod(m, 60)
- return '%dh, %dm, %ds' % (h, m, s)
-
- def log_info(self, msg):
- logging.info(self._name + ': %s' % msg)
-
- def tick(self):
- if not self._enabled:
- return
-
-#### GuiMods warm-up / cool-down
- self._currentTime = self._get_monotonic_seconds ()
-
- self._check_remote_status()
-#### GuiMods
- self._linkToExternalState = self._settings['linkManualStartToExternal'] == 1
- self._processGeneratorRunDetection ()
-
- self._evaluate_startstop_conditions()
- self._evaluate_autostart_disabled_alarm()
- self._detect_generator_at_acinput()
- if self._dbusservice['/ServiceCounterReset'] == 1:
- self._dbusservice['/ServiceCounterReset'] = 0
-
-#### GuiMods warm-up / cool-down
- state = self._dbusservice['/State']
-
- # shed load for active generator input in warm-up and cool-down
- # note that external transfer switch might change the state of on generator
- # so this needs to be checked and load adjusted every pass
- # restore load for sources no longer in use or if state is not in warm-up/cool-down
- # restoring load is delayed 1following end of cool-down
- # to allow the generator to actually stop producing power
- if state in (States.WARMUP, States.COOLDOWN, States.STOPPING):
- self._set_ignore_ac (True)
- else:
- self._set_ignore_ac (False)
-
- # update cool down end time while running and generator has the load
- # this is done because acInIsGenerator may change by an external transfer switch
- # and we want an accurate picture of the cool down end time
- # based on the last time the generatot was loaded
- if state == States.RUNNING and self._acInIsGenerator:
- self._coolDownEndTime = self._currentTime + self._settings['cooldowntime']
-#### end GuiMods warm-up / cool-down
-
-
- def _evaluate_startstop_conditions(self):
- if self.get_error() != Errors.NONE:
- # First evaluation after an error, log it
- if self._errorstate == 0:
- self._errorstate = 1
- self._dbusservice['/State'] = States.ERROR
- self.log_info('Error: #%i - %s, stop controlling remote.' %
- (self.get_error(),
- Errors.get_description(self.get_error())))
- elif self._errorstate == 1:
- # Error cleared
- self._errorstate = 0
- self._dbusservice['/State'] = States.STOPPED
- self.log_info('Error state cleared, taking control of remote switch.')
-
- start = False
- startbycondition = None
- activecondition = self._dbusservice['/RunningByCondition']
- today = calendar.timegm(datetime.date.today().timetuple())
- self._timer_runnning = False
- connection_lost = False
- running = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP)
-
- self._check_quiet_hours()
-
- # New day, register it
- if self._last_counters_check < today and self._dbusservice['/State'] == States.STOPPED:
- self._last_counters_check = today
- self._update_accumulated_time()
-
- # Update current and accumulated runtime.
-#### GuiMods
- self._accumulateRunTime ()
-
-#### GuiMods
- # A negative /ManualStartTimer is used by the GUI to signal the generator should start now
- # but stop when all auto stop conditions have been met
- # so we skip manual start evaluation if this is the case
- # and set a flag for use below to ignore auto start conditions
- # the generator is actually started by the auto start/stop logic below
- if self._dbusservice['/ManualStartTimer'] < 0 and self._dbusservice['/ManualStart'] == 1:
- self._dbusservice['/ManualStartTimer'] = 0
- self._dbusservice['/ManualStart'] = 0
- self._ignoreAutoStartCondition = True
-
- else:
- self._ignoreAutoStartCondition = False
- if self._evaluate_manual_start():
- startbycondition = 'manual'
- start = True
-
- # Conditions will only be evaluated if the autostart functionality is enabled
- if self._settings['autostart'] == 1:
-
- if self._evaluate_testrun_condition():
- startbycondition = 'testrun'
- start = True
-
- # Evaluate stop on AC IN conditions first, when this conditions are enabled and reached the generator
- # will stop as soon as AC IN in active. Manual and testrun conditions will make the generator start
- # or keep it running.
- stop_on_ac_reached = (self._evaluate_condition(self._condition_stack[StopOnAc1Condition.name]) or
- self._evaluate_condition(self._condition_stack[StopOnAc2Condition.name]))
- stop_by_ac1_ac2 = startbycondition not in ['manual', 'testrun'] and stop_on_ac_reached
-
- if stop_by_ac1_ac2 and running and activecondition not in ['manual', 'testrun']:
- self.log_info('AC input available, stopping')
-
- # Evaluate value conditions
- for condition, data in self._condition_stack.items():
- # Do not evaluate rest of conditions if generator is configured to stop
- # when AC IN is available
- if stop_by_ac1_ac2:
- start = False
- if running:
- self._reset_condition(data)
- continue
- else:
- break
-
- # Don't short-circuit this, _evaluate_condition sets .reached
- start = self._evaluate_condition(data) or start
- startbycondition = condition if start and startbycondition is None else startbycondition
- # Connection lost is set to true if the number of retries of one or more enabled conditions
- # >= RETRIES_ON_ERROR
- if data.enabled:
- connection_lost = data.retries >= self.RETRIES_ON_ERROR
-
- # If none condition is reached check if connection is lost and start/keep running the generator
- # depending on '/OnLossCommunication' setting
- if not start and connection_lost:
- # Start always
- if self._settings['onlosscommunication'] == 1:
- start = True
- startbycondition = 'lossofcommunication'
- # Keep running if generator already started
- if running and self._settings['onlosscommunication'] == 2:
- start = True
- startbycondition = 'lossofcommunication'
-
-#### GuiMods
- ## auto start disabled and generator is stopped - clear the 'reached' flags
- elif self._dbusservice['/State'] == States.STOPPED:
- for condition, data in self._condition_stack.items():
- self._reset_condition(data)
-
- if not start and self._errorstate:
- self._stop_generator()
-
- if self._errorstate:
- return
-
- if start:
- self._start_generator(startbycondition)
-#### GuiMods
- # bypass the minimum run time check if External Override is active
- elif (self._dbusservice['/Runtime'] >= self._settings['minimumruntime'] * 60
- or activecondition == 'manual') or self._dbusservice['/ExternalOverride']:
- self._stop_generator()
-
- def _evaluate_autostart_disabled_alarm(self):
-
- if self._settings['autostartdisabledalarm'] == 0:
- self._autostart_last_time = self._get_monotonic_seconds()
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
- if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
- self._dbusservice['/Alarms/AutoStartDisabled'] = 0
- if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
- self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0
- return
-
- # GX auto start/stop alarm
- if self._settings['autostart'] == 1:
- self._autostart_last_time = self._get_monotonic_seconds()
- if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
- self._dbusservice['/Alarms/AutoStartDisabled'] = 0
- else:
- timedisabled = self._get_monotonic_seconds() - self._autostart_last_time
- if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/AutoStartDisabled'] != 2:
- self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
- self._dbusservice['/Alarms/AutoStartDisabled'] = 2
-
- # Genset remote start mode alarm
- if self.get_error() != Errors.REMOTEDISABLED:
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
- if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
- self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0
- else:
- timedisabled = self._get_monotonic_seconds() - self._remote_start_mode_last_time
- if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 2:
- self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
- self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 2
-
-
-#### GuiMods warm-up / cool-down - rewrote so acInIsGenerator is updated even if alarm is disabled
- def _detect_generator_at_acinput(self):
-#### GuiMods warm-up / cool-down
- self._acInIsGenerator = False # covers all conditions that result in a return
-
- state = self._dbusservice['/State']
- if state in [States.STOPPED, States.COOLDOWN, States.WARMUP]:
- self._reset_acpower_inverter_input()
- return
-
- vebus_service = self._vebusservice if self._vebusservice else ''
- activein_state = self._dbusmonitor.get_value(
- vebus_service, '/Ac/ActiveIn/Connected')
-
- # Path not supported, skip evaluation
- if activein_state == None:
- return
-
- # Sources 0 = Not available, 1 = Grid, 2 = Generator, 3 = Shore
- generator_acsource = self._dbusmonitor.get_value(
- SYSTEM_SERVICE, '/Ac/ActiveIn/Source') == 2
- # Not connected = 0, connected = 1
- activein_connected = activein_state == 1
-
-#### GuiMods warm-up / cool-down
- if self._settings['nogeneratoratacinalarm'] == 0:
- processAlarm = False
- self._reset_acpower_inverter_input()
- else:
- processAlarm = True
-
- if generator_acsource and activein_connected:
-#### GuiMods warm-up / cool-down
- self._acInIsGenerator = True
-#### GuiMods warm-up / cool-down
- if processAlarm and self._acpower_inverter_input['unabletostart']:
- self.log_info('Generator detected at inverter AC input, alarm removed')
- self._reset_acpower_inverter_input()
-#### GuiMods warm-up / cool-down
- elif not processAlarm:
- self._reset_acpower_inverter_input()
- return
- elif self._acpower_inverter_input['timeout'] < self.RETRIES_ON_ERROR:
- self._acpower_inverter_input['timeout'] += 1
- elif not self._acpower_inverter_input['unabletostart']:
- self._acpower_inverter_input['unabletostart'] = True
- self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 2
- self.log_info('Generator not detected at inverter AC input, triggering alarm')
-
-
- def _reset_acpower_inverter_input(self, clear_error=True):
- if self._acpower_inverter_input['timeout'] != 0:
- self._acpower_inverter_input['timeout'] = 0
-
- if self._acpower_inverter_input['unabletostart'] != 0:
- self._acpower_inverter_input['unabletostart'] = 0
-
- self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 0
-
- def _reset_condition(self, condition):
- condition['reached'] = False
- if condition['timed']:
- condition['start_timer'] = 0
- condition['stop_timer'] = 0
-
- def _check_condition(self, condition, value):
- name = condition['name']
-
- if self._settings[name + 'enabled'] == 0:
- if condition['enabled']:
- condition['enabled'] = False
- self.log_info('Disabling (%s) condition' % name)
- condition['retries'] = 0
- condition['valid'] = True
- self._reset_condition(condition)
- return False
-
- elif not condition['enabled']:
- condition['enabled'] = True
- self.log_info('Enabling (%s) condition' % name)
-
- if (condition['monitoring'] == 'battery') and (self._settings['batterymeasurement'] == 'nobattery'):
- # If no battery monitor is selected reset the condition
- self._reset_condition(condition)
- return False
-
- if value is None and condition['valid']:
- if condition['retries'] >= self.RETRIES_ON_ERROR:
- logging.info('Error getting (%s) value, skipping evaluation till get a valid value' % name)
- self._reset_condition(condition)
- self._comunnication_lost = True
- condition['valid'] = False
- else:
- condition['retries'] += 1
- if condition['retries'] == 1 or (condition['retries'] % 10) == 0:
- self.log_info('Error getting (%s) value, retrying(#%i)' % (name, condition['retries']))
- return False
-
- elif value is not None and not condition['valid']:
- self.log_info('Success getting (%s) value, resuming evaluation' % name)
- condition['valid'] = True
- condition['retries'] = 0
-
- # Reset retries if value is valid
- if value is not None and condition['retries'] > 0:
- self.log_info('Success getting (%s) value, resuming evaluation' % name)
- condition['retries'] = 0
-
- return condition['valid']
-
- def _evaluate_condition(self, condition):
- name = condition['name']
- value = condition.get_value()
- setting = ('qh_' if self._dbusservice['/QuietHours'] == 1 else '') + name
- startvalue = self._settings[setting + 'start'] if not condition['boolean'] else 1
- stopvalue = self._settings[setting + 'stop'] if not condition['boolean'] else 0
-
- # Check if the condition has to be evaluated
- if not self._check_condition(condition, value):
- # If generator is started by this condition and value is invalid
- # wait till RETRIES_ON_ERROR to skip the condition
- if condition['reached'] and condition['retries'] <= self.RETRIES_ON_ERROR:
- if condition['retries'] > 0:
- return True
-
- return False
-
- # As this is a generic evaluation method, we need to know how to compare the values
- # first check if start value should be greater than stop value and then compare
- start_is_greater = startvalue > stopvalue
-
-#### GuiMods
- stop = value <= stopvalue if start_is_greater else value >= stopvalue
- # when starting manually and stopping based on auto stop values,
- # start if stop condition is not satisfied
-
- if self._ignoreAutoStartCondition:
- start = not stop
- else:
- # When the condition is already reached only the stop value can set it to False
- start = condition['reached'] or (value >= startvalue if start_is_greater else value <= startvalue)
-
- # Timed conditions must start/stop after the condition has been reached for a minimum
- # time.
- if condition['timed']:
- if not condition['reached'] and start:
- condition['start_timer'] += time.time() if condition['start_timer'] == 0 else 0
- start = time.time() - condition['start_timer'] >= self._settings[name + 'starttimer']
- condition['stop_timer'] *= int(not start)
- self._timer_runnning = True
- else:
- condition['start_timer'] = 0
-
- if condition['reached'] and stop:
- condition['stop_timer'] += time.time() if condition['stop_timer'] == 0 else 0
- stop = time.time() - condition['stop_timer'] >= self._settings[name + 'stoptimer']
- condition['stop_timer'] *= int(not stop)
- self._timer_runnning = True
- else:
- condition['stop_timer'] = 0
-
- condition['reached'] = start and not stop
- return condition['reached']
-
- def _evaluate_manual_start(self):
- if self._dbusservice['/ManualStart'] == 0:
- if self._dbusservice['/RunningByCondition'] == 'manual':
- self._dbusservice['/ManualStartTimer'] = 0
- return False
-
- start = True
- # If /ManualStartTimer has a value greater than zero will use it to set a stop timer.
- # If no timer is set, the generator will not stop until the user stops it manually.
- # Once started by manual start, each evaluation the timer is decreased
-#### GuiMods
- if self._dbusservice['/ManualStartTimer'] > 0:
- self._manualstarttimer += time.time() if self._manualstarttimer == 0 else 0
- self._dbusservice['/ManualStartTimer'] -= int(time.time()) - int(self._manualstarttimer)
- self._manualstarttimer = time.time()
- start = self._dbusservice['/ManualStartTimer'] > 0
- self._dbusservice['/ManualStart'] = int(start)
- # Reset if timer is finished
- self._manualstarttimer *= int(start)
- self._dbusservice['/ManualStartTimer'] *= int(start)
-
- return start
-
- def _evaluate_testrun_condition(self):
- if self._settings['testrunenabled'] == 0:
- self._dbusservice['/SkipTestRun'] = None
- self._dbusservice['/NextTestRun'] = None
- return False
-
- today = datetime.date.today()
- yesterday = today - datetime.timedelta(days=1) # Should deal well with DST
- now = time.time()
- runtillbatteryfull = self._settings['testruntillbatteryfull'] == 1
- soc = self._condition_stack['soc'].get_value()
- batteryisfull = runtillbatteryfull and soc == 100
- duration = 60 if runtillbatteryfull else self._settings['testrunruntime']
-
- try:
- startdate = datetime.date.fromtimestamp(self._settings['testrunstartdate'])
- _starttime = time.mktime(yesterday.timetuple()) + self._settings['testrunstarttimer']
-
- # today might in fact still be yesterday, if this test run started
- # before midnight and finishes after. If `now` still falls in
- # yesterday's window, then by the temporal anthropic principle,
- # which I just made up but loosely states that time must have
- # these properties for observers to exist, it must be yesterday
- # because we are here to observe it.
- if _starttime <= now <= _starttime + duration:
- today = yesterday
- starttime = _starttime
- else:
- starttime = time.mktime(today.timetuple()) + self._settings['testrunstarttimer']
- except ValueError:
- logging.debug('Invalid dates, skipping testrun')
- return False
-
- # If start date is in the future set as NextTestRun and stop evaluating
- if startdate > today:
- self._dbusservice['/NextTestRun'] = time.mktime(startdate.timetuple())
- return False
-
- start = False
- # If the accumulated runtime during the tes trun interval is greater than '/TestRunIntervalRuntime'
- # the tes trun must be skipped
- needed = (self._settings['testrunskipruntime'] > self._dbusservice['/TestRunIntervalRuntime']
- or self._settings['testrunskipruntime'] == 0)
- self._dbusservice['/SkipTestRun'] = int(not needed)
-
- interval = self._settings['testruninterval']
- stoptime = starttime + duration
- elapseddays = (today - startdate).days
- mod = elapseddays % interval
-
- start = not bool(mod) and starttime <= now <= stoptime
-
- if runtillbatteryfull:
- if soc is not None:
- self._testrun_soc_retries = 0
- start = (start or self._dbusservice['/RunningByCondition'] == 'testrun') and not batteryisfull
- elif self._dbusservice['/RunningByCondition'] == 'testrun':
- if self._testrun_soc_retries < self.RETRIES_ON_ERROR:
- self._testrun_soc_retries += 1
- start = True
- if (self._testrun_soc_retries % 10) == 0:
- self.log_info('Test run failed to get SOC value, retrying(#%i)' % self._testrun_soc_retries)
- else:
- self.log_info('Failed to get SOC after %i retries, terminating test run condition' % self._testrun_soc_retries)
- start = False
- else:
- start = False
-
- if not bool(mod) and (now <= stoptime):
- self._dbusservice['/NextTestRun'] = starttime
- else:
- self._dbusservice['/NextTestRun'] = (time.mktime((today + datetime.timedelta(days=interval - mod)).timetuple()) +
- self._settings['testrunstarttimer'])
- return start and needed
-
- def _check_quiet_hours(self):
- active = False
- if self._settings['quiethoursenabled'] == 1:
- # Seconds after today 00:00
- timeinseconds = time.time() - time.mktime(datetime.date.today().timetuple())
- quiethoursstart = self._settings['quiethoursstarttime']
- quiethoursend = self._settings['quiethoursendtime']
-
- # Check if the current time is between the start time and end time
- if quiethoursstart < quiethoursend:
- active = quiethoursstart <= timeinseconds and timeinseconds < quiethoursend
- else: # End time is lower than start time, example Start: 21:00, end: 08:00
- active = not (quiethoursend < timeinseconds and timeinseconds < quiethoursstart)
-
- if self._dbusservice['/QuietHours'] == 0 and active:
- self.log_info('Entering to quiet mode')
-
- elif self._dbusservice['/QuietHours'] == 1 and not active:
- self.log_info('Leaving quiet mode')
-
- self._dbusservice['/QuietHours'] = int(active)
-
- return active
-
- def _update_accumulated_time(self):
- seconds = self._dbusservice['/Runtime']
- accumulated = seconds - self._last_runtime_update
-
- self._settings['accumulatedtotal'] = accumulatedtotal = int(self._settings['accumulatedtotal']) + accumulated
- # Using calendar to get timestamp in UTC, not local time
- today_date = str(calendar.timegm(datetime.date.today().timetuple()))
-
- # If something goes wrong getting the json string create a new one
- try:
- accumulated_days = json.loads(self._settings['accumulateddaily'])
- except ValueError:
- accumulated_days = {today_date: 0}
-
- if (today_date in accumulated_days):
- accumulated_days[today_date] += accumulated
- else:
- accumulated_days[today_date] = accumulated
-
- self._last_runtime_update = seconds
-
- # Keep the historical with a maximum of HISTORY_DAYS
- while len(accumulated_days) > HISTORY_DAYS:
- accumulated_days.pop(min(accumulated_days.keys()), None)
-
- # Upadate settings
- self._settings['accumulateddaily'] = json.dumps(accumulated_days, sort_keys=True)
- self._dbusservice['/TodayRuntime'] = self._interval_runtime(0)
- self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime(self._settings['testruninterval'])
- self._dbusservice['/AccumulatedRuntime'] = accumulatedtotal
-
- # Service counter
- serviceinterval = self._settings['serviceinterval']
- lastservicereset = self._settings['lastservicereset']
- if serviceinterval > 0:
- servicecountdown = (lastservicereset + serviceinterval) - accumulatedtotal
- self._dbusservice['/ServiceCounter'] = servicecountdown
- if servicecountdown <= 0:
- self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 1
- elif self._dbusservice['/Alarms/ServiceIntervalExceeded'] != 0:
- self._dbusservice['/Alarms/ServiceIntervalExceeded'] = 0
-
-
-
- def _interval_runtime(self, days):
- summ = 0
- try:
- daily_record = json.loads(self._settings['accumulateddaily'])
- except ValueError:
- return 0
-
- for i in range(days + 1):
- previous_day = calendar.timegm((datetime.date.today() - datetime.timedelta(days=i)).timetuple())
- if str(previous_day) in daily_record.keys():
- summ += daily_record[str(previous_day)] if str(previous_day) in daily_record.keys() else 0
-
- return summ
-
- def _get_battery(self):
- if self._settings['batterymeasurement'] == 'default':
- return Battery(self._dbusmonitor, SYSTEM_SERVICE, BATTERY_PREFIX)
-
- return Battery(self._dbusmonitor,
- self._battery_service if self._battery_service else '',
- self._battery_prefix if self._battery_prefix else '')
-
- def _set_capabilities(self):
- # Update capabilities
- # The ability to ignore AC1/AC2 came in at the same time as
- # AC availability and is used to detect it here.
- readout_supported = self._dbusmonitor.get_value(self._vebusservice,
- '/Ac/State/AcIn1Available') is not None
- self._dbusservice['/Capabilities'] |= (
- Capabilities.WarmupCooldown if readout_supported else 0)
-
- def _determineservices(self):
- # batterymeasurement is either 'default' or 'com_victronenergy_battery_288/Dc/0'.
- # In case it is set to default, we use the AutoSelected battery
- # measurement, given by SystemCalc.
- batterymeasurement = None
- newbatteryservice = None
- batteryprefix = ''
- selectedbattery = self._settings['batterymeasurement']
- vebusservice = None
-
- if selectedbattery == 'default':
- batterymeasurement = 'default'
- elif len(selectedbattery.split('/', 1)) == 2: # Only very basic sanity checking..
- batterymeasurement = self._settings['batterymeasurement']
- elif selectedbattery == 'nobattery':
- batterymeasurement = None
- else:
- # Exception: unexpected value for batterymeasurement
- pass
-
- if batterymeasurement and batterymeasurement != 'default':
- batteryprefix = '/' + batterymeasurement.split('/', 1)[1]
-
- # Get the current battery servicename
- if self._battery_service:
- oldservice = self._battery_service
- else:
- oldservice = None
-
- if batterymeasurement != 'default':
- battery_instance = int(batterymeasurement.split('_', 3)[3].split('/')[0])
- service_type = None
-
- if 'vebus' in batterymeasurement:
- service_type = 'vebus'
- elif 'battery' in batterymeasurement:
- service_type = 'battery'
-
- newbatteryservice = self._get_servicename_by_instance(battery_instance, service_type)
- elif batterymeasurement == 'default':
- newbatteryservice = 'default'
-
- if newbatteryservice and newbatteryservice != oldservice:
- if selectedbattery == 'default':
- self.log_info('Getting battery values from systemcalc.')
- if selectedbattery == 'nobattery':
- self.log_info('Battery monitoring disabled! Stop evaluating related conditions')
- self._battery_service = None
- self._battery_prefix = None
- self.log_info('Battery service we need (%s) found! Using it for generator start/stop' % batterymeasurement)
- self._battery_service = newbatteryservice
- self._battery_prefix = batteryprefix
- elif not newbatteryservice and newbatteryservice != oldservice:
- self.log_info('Error getting battery service!')
- self._battery_service = newbatteryservice
- self._battery_prefix = batteryprefix
-
- # Get the default VE.Bus service
- vebusservice = self._dbusmonitor.get_value('com.victronenergy.system', '/VebusService')
- if vebusservice:
- if self._vebusservice != vebusservice:
- self._vebusservice = vebusservice
- self._set_capabilities()
- self.log_info('Vebus service (%s) found! Using it for generator start/stop' % vebusservice)
- else:
- if self._vebusservice is not None:
- self.log_info('Vebus service (%s) dissapeared! Stop evaluating related conditions' % self._vebusservice)
- else:
- self.log_info('Error getting Vebus service!')
- self._vebusservice = None
-
- def _get_servicename_by_instance(self, instance, service_type=None):
- sv = None
- services = self._dbusmonitor.get_service_list()
-
- for service in services:
- if service_type and service_type not in service:
- continue
-
- if services[service] == instance:
- sv = service
- break
-
- return sv
-
- def _get_monotonic_seconds(self):
- return monotonic_time.monotonic_time().to_seconds_double()
-
- def _start_generator(self, condition):
- state = self._dbusservice['/State']
- remote_running = self._get_remote_switch_state()
-
- # This function will start the generator in the case generator not
- # already running. When differs, the RunningByCondition is updated
- running = state in (States.WARMUP, States.COOLDOWN, States.STOPPING, States.RUNNING)
- if not (running and remote_running): # STOPPED, ERROR
-#### GuiMods warm-up / cool-down
- self.log_info('Starting generator by %s condition' % condition)
- # if there is a warmup time specified, always go through warm-up state
- # regardless of AC input in use
- warmUpPeriod = self._settings['warmuptime']
- if warmUpPeriod > 0:
- self._warmUpEndTime = self._currentTime + warmUpPeriod
- self.log_info ("starting warm-up")
- self._dbusservice['/State'] = States.WARMUP
- # no warm-up go directly to running
- else:
- self._dbusservice['/State'] = States.RUNNING
- self._warmUpEndTime = 0
-
- self._coolDownEndTime = 0
- self._stoptime = 0
-
- self._update_remote_switch()
- else: # WARMUP, COOLDOWN, RUNNING, STOPPING
- if state in (States.COOLDOWN, States.STOPPING):
- # Start request during cool-down run, go back to RUNNING
- self.log_info ("aborting cool-down - returning to running")
- self._dbusservice['/State'] = States.RUNNING
-
- elif state == States.WARMUP:
- if self._currentTime > self._warmUpEndTime:
- self.log_info ("warm-up complete")
- self._dbusservice['/State'] = States.RUNNING
-
- # Update the RunningByCondition
- if self._dbusservice['/RunningByCondition'] != condition:
- self.log_info('Generator previously running by %s condition is now running by %s condition'
- % (self._dbusservice['/RunningByCondition'], condition))
-#### end GuiMods warm-up / cool-down
-
-
- self._dbusservice['/RunningByCondition'] = condition
- self._dbusservice['/RunningByConditionCode'] = RunningConditions.lookup(condition)
-
-
- def _stop_generator(self):
- state = self._dbusservice['/State']
- remote_running = self._get_remote_switch_state()
- running = state in (States.WARMUP, States.COOLDOWN, States.STOPPING, States.RUNNING)
-
- if running or remote_running:
-#### GuiMods warm-up / cool-down
- if state == States.RUNNING:
- state = States.COOLDOWN
- if self._currentTime < self._coolDownEndTime:
- self.log_info ("starting cool-down")
- elif self._settings['cooldowntime'] != 0:
- self.log_info ("skipping cool-down -- no AC load on generator")
-
- # warm-up should also transition to stopping
- # cool-down time will have expired since it's set to 0 when starting
- # and there has not yet been a load on the generator
- if state in (States.WARMUP, States.COOLDOWN):
- # cool down complete
- if self._currentTime > self._coolDownEndTime:
- state = States.STOPPING
- self.log_info('Stopping generator that was running by %s condition' %
- str(self._dbusservice['/RunningByCondition']))
- self._update_remote_switch() # Stop engine
- self._stoptime = self._currentTime + self._settings['generatorstoptime']
- if self._currentTime < self._stoptime:
- self.log_info ("waiting for generator so stop")
-
- if state == States.STOPPING:
- # wait for stop period expired - finish up transition to STOPPED
- if self._currentTime > self._stoptime:
- if self._settings['generatorstoptime'] != 0:
- self.log_info ("generator stop time reached - OK to reconnect AC")
- state = States.STOPPED
- self._update_remote_switch()
- self._dbusservice['/RunningByCondition'] = ''
- self._dbusservice['/RunningByConditionCode'] = RunningConditions.Stopped
- self._update_accumulated_time()
- self._starttime = 0
- self._dbusservice['/Runtime'] = 0
- self._dbusservice['/ManualStartTimer'] = 0
- self._manualstarttimer = 0
- self._last_runtime_update = 0
-
- self._dbusservice['/State'] = state
-#### end GuiMods warm-up / cool-down
-
-
- @property
- def _ac1_is_generator(self):
- return self._dbusmonitor.get_value('com.victronenergy.settings',
- '/Settings/SystemSetup/AcInput1') == 2
-
- @property
- def _ac2_is_generator(self):
- return self._dbusmonitor.get_value('com.victronenergy.settings',
- '/Settings/SystemSetup/AcInput2') == 2
-
- def _set_ignore_ac(self, ignore):
- # This is here so the Multi/Quattro can be told to disconnect AC-in,
- # so that we can do warm-up and cool-down.
-#### GuiMods warm-up / cool-down
- # stock code does not handle changes in the input type
- # which could happen with an external transfer switch
- # doing things this way should handle it
-
- self._activeAcInIsIgnored = ignore
- ignore1 = False
- ignore2 = False
- if self._ac1_is_generator:
- ignore1 = ignore
- elif self._ac2_is_generator:
- ignore2 = ignore
-
- if ignore1 != self._ac1isIgnored:
- if ignore1:
- self.log_info ("shedding load - AC input 1")
- else:
- self.log_info ("restoring load - AC input 1")
- self._dbusmonitor.set_value_async(self._vebusservice, '/Ac/Control/IgnoreAcIn1', dbus.Int32(ignore1, variant_level=1))
- self._ac1isIgnored = ignore1
-
- if ignore2 != self._ac2isIgnored:
- if ignore2:
- self.log_info ("shedding load - AC input 2")
- else:
- self.log_info ("restoring load - AC input 2")
- self._dbusmonitor.set_value_async(self._vebusservice, '/Ac/Control/IgnoreAcIn2', dbus.Int32(ignore2, variant_level=1))
- self._ac2isIgnored = ignore2
-#### end GuiMods warm-up / cool-down
-
-
- def _update_remote_switch(self):
- # Engine should be started in these states
- v = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN)
- self._set_remote_switch_state(dbus.Int32(v, variant_level=1))
-#### GuiMods
- if v == True:
- self.log_info ("updating remote switch to running")
- else:
- self.log_info ("updating remote switch to stopped")
-
- def _get_remote_switch_state(self):
- raise Exception('This function should be overridden')
-
- def _set_remote_switch_state(self, value):
- raise Exception('This function should be overridden')
-
- # Check the remote status, for example errors
- def _check_remote_status(self):
- raise Exception('This function should be overridden')
-
- def _remote_setup(self):
- raise Exception('This function should be overridden')
-
- 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):
- return create_dbus_service(self._instance)
-
-
-#### GuiMods
-
-# this function connects the generator digital input (if any)
-# OR the generator AC input detection
-# to the generator /ManualStart and updates dbus paths used by the GUI
-#
-# if the generator digital input changes from stopped to running
-# AND no run conditions are active, a manual start is innitiated
-#
-# if the generator digital input changes from running to stopped
-# AND a manual start is active, a manual stop is innitiated
-#
-# /GeneratorRunningState provides the input running state from the digital input to the GUI
-# R = running
-# S = stopped
-# ? = unknown (no digital input found)
-#
-# /ExternalOverride is used by the GUI to alert the user when there is a conflict
-# between the generator running state and the state Venus
-# /ExternalOverride is True if /GeneratorRunningState is S
-# AND the /RunningCondition is not stopped (which includes a manual run)
-# activation is delayed 5 seconds to allow transitions to settle
-#
-# we must first find the geneator digital input, if it exists at all
-# we serche all dBus services looking for a digital input with type generator (9)
-# the search only occurs every 10 seconds
-#
-
- def _processGeneratorRunDetection (self):
- TheBus = dbus.SystemBus()
- generatorState = self._dbusservice['/State']
- try:
- # current input service is no longer valid
- # search for a new one only every 10 seconds to avoid unnecessary processing
- if (self._digitalInputTypeObject == None or self._digitalInputTypeObject.GetValue() != 9) and self._searchDelay > 10:
- newInputService = ""
- for service in TheBus.list_names():
- # found a digital input servic, now check the type
- if service.startswith ("com.victronenergy.digitalinput"):
- self._digitalInputTypeObject = TheBus.get_object (service, '/Type')
- # found it!
- if self._digitalInputTypeObject.GetValue() == 9:
- newInputService = service
- break
-
- # found new service - get objects for use later
- if newInputService != "":
- self.log_info ("Found generator digital input service at %s" % newInputService)
- self._generatorInputStateObject = TheBus.get_object(newInputService, '/State')
- else:
- if self._generatorInputStateObject != None:
- self.log_info ("Generator digital input service NOT found")
- self._generatorInputStateObject = None
- self._digitalInputTypeObject = None
- self._searchDelay = 0 # start delay timer
-
- # if serch delay timer is active, increment it now
- if self._searchDelay <= 10:
- self._searchDelay += 1
-
-
- # collect generator input states
- inputState = '?'
- # if generator digital input is present, use that
- if self._generatorInputStateObject != None:
- inputState = self._generatorInputStateObject.GetValue ()
- if inputState == 10:
- inputState = 'R'
- elif inputState == 11:
- inputState = 'S'
- # otherwise use generator AC input to determine running state
- # use frequency as the test for generator running
- elif self._ac1_is_generator or self._ac2_is_generator:
- try:
- if self._dbusmonitor.get_value (SYSTEM_SERVICE, '/Ac/Genset/Frequency') > 20:
- inputState = 'R'
- else:
- inputState = 'S'
- except:
- pass
-
- # update /GeneratorRunningState
- if inputState != self._lastState:
- self._dbusservice['/GeneratorRunningState'] = inputState
-
- # forward input state changes to /ManualStart
- if self._linkToExternalState:
- if inputState == "R" and generatorState == States.STOPPED:
- self.log_info ("generator was started externally - syncing ManualStart state")
- self._dbusservice['/ManualStart'] = 1
- elif inputState == "S" and self._dbusservice['/ManualStart'] == 1 \
- and generatorState in (States.RUNNING, States.WARMUP, States.COOLDOWN):
- self.log_info ("generator was stopped externally - syncing ManualStart state")
- self._dbusservice['/ManualStart'] = 0
-
- # update /ExternalOverride
- if inputState == "S" and self._linkToExternalState and generatorState == States.RUNNING:
- if self._externalOverrideDelay > 5:
- self._externalOverride = True
- else:
- self._externalOverrideDelay += 1
- else:
- self._externalOverride = False
- self._externalOverrideDelay = 0
-
- if self._externalOverride != self._lastExternalOverride:
- self._dbusservice['/ExternalOverride'] = self._externalOverride
- self._lastExternalOverride = self._externalOverride
-
- except dbus.DBusException:
- self.log_info ("dbus exception - generator digital input no longer valid")
- self._generatorInputStateObject = None
- self._digitalInputTypeObject = None
- inputState = 0
-
- self._lastState = inputState
-
-
-#
-# control the accumulaiton of run time based on generator input Running state
-# if the internal state is RUNNING run time is accumulated in self._accumulatedRunTime
-# run time is accumulated if the generator's running state is known to be running or
-# if the generator running state can't be determined
-# the accumulated time dBus parameter and daily and total time accumulators are updated
-# only once everh 60 seconds to minimize processor load
-# if the internal state is STOPPED, one last dBus, daily and total time updates are done
-# then the current time accumulator is cleared
-
- def _accumulateRunTime (self):
-
- # grab running state from dBus once, use it many timed below
-
- if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING): ##########
- internalRun = True
- else:
- internalRun = False
-
- # if internal state is running, accumulate time if generator is running
- if internalRun:
- accumuateTime = True
- # start new accumulation if not done prevously
- if self._last_accumulate_time == 0:
- self._last_accumulate_time = self._currentTime
-
- # if link to external state is enabled, don't accumulate time if running state is stopped
- # (accumulate if R or ?)
- if self._linkToExternalState:
- try:
- if self._dbusservice['/GeneratorRunningState'] == 'S':
- accumuateTime = False
-
- # if no Forwarder service, allow accumulation
- except dbus.DBusException:
- self.log_info ("dBus exception in startstop.py")
-
- # internal state STOPPED so don't add new time to the accumulation
- # but there may be time already accumulated that needs to be added to daily and total accumulations
- else:
- accumuateTime = False
-
- # accumulate run time if we passed all the tests above
- if accumuateTime:
- self._accumulatedRunTime += self._currentTime - self._last_accumulate_time
- self._last_accumulate_time = self._currentTime
-
- # dbus and settings updates trigger time-intensive processing so only do this once every 60 seconds
- doUpdate = False
- if internalRun:
- if self._currentTime - self._last_update_mtime >= 60:
- doUpdate = True
- self._last_update_mtime = self._currentTime
- # it is also done one last time when state is no longer RUNNING
- elif self._last_update_mtime != 0:
- doUpdate = True
-
- if doUpdate:
- self._update_accumulated_time()
-
- # stopped - clear the current time accumulator
- if internalRun == False:
- self._last_update_mtime = 0
- self._accumulatedRunTime = 0
- self._last_accumulate_time = 0
-
- self._dbusservice['/Runtime'] = int(self._accumulatedRunTime)
-#### end GuiMods
diff --git a/FileSets/v3.50~25/COMPLETE b/FileSets/v3.50~25/COMPLETE
deleted file mode 100644
index e69de29b..00000000
diff --git a/FileSets/v3.50~25/OverviewGeneratorEnhanced.qml b/FileSets/v3.50~25/OverviewGeneratorEnhanced.qml
deleted file mode 100644
index 0c189315..00000000
--- a/FileSets/v3.50~25/OverviewGeneratorEnhanced.qml
+++ /dev/null
@@ -1,547 +0,0 @@
-// GuiMods enhanced generator overview
-// This file has been modified to:
-// add Auto Start display and control
-// show voltage, current, frequency, and power gauge in AC input tile
-// show the generator running state inside the icon top left
-// show a warning when the generator digital input and expected generator state disagree
-// move current run time to separate tile
-
-import QtQuick 1.1
-import "utils.js" as Utils
-import "enhancedFormat.js" as EnhFmt
-
-OverviewPage {
- id: root
-
- property string settingsBindPrefix
- property string bindPrefix
- property variant sys: theSystem
-//////// added to show alternator in place of inactive genset
- property string guiModsPrefix: "com.victronenergy.settings/Settings/GuiMods"
- VBusItem { id: replaceAcInItem; bind: Utils.path(guiModsPrefix, "/ReplaceInactiveAcIn") }
- property bool hasAlternator: sys.alternator.power.valid
- property bool showAlternator: replaceAcInItem.valid && replaceAcInItem.value == 1 && hasAlternator && ! sys.genset.power.valid
- property bool showAcIn: ! showAlternator
-
- property string icon: "overview-generator"
- property VBusItem state: VBusItem { bind: Utils.path(bindPrefix, "/State") }
- property VBusItem error: VBusItem { bind: Utils.path(bindPrefix, "/Error") }
- property VBusItem runningTime: VBusItem { bind: Utils.path(bindPrefix, "/Runtime") }
- property VBusItem runningBy: VBusItem { bind: Utils.path(bindPrefix, "/RunningByConditionCode") }
- VBusItem { id: totalAcummulatedTime; bind: Utils.path(settingsBindPrefix, "/AccumulatedTotal") }
- VBusItem { id: totalAccumulatedTimeOffset; bind: Utils.path(settingsBindPrefix, "/AccumulatedTotalOffset") }
- property VBusItem quietHours: VBusItem { bind: Utils.path(bindPrefix, "/QuietHours") }
- property VBusItem testRunDuration: VBusItem { bind: Utils.path(settingsBindPrefix, "/TestRun/Duration") }
- property VBusItem nextTestRun: VBusItem { bind: Utils.path(bindPrefix, "/NextTestRun") }
- property VBusItem skipTestRun: VBusItem { bind: Utils.path(bindPrefix, "/SkipTestRun") }
-
- property VBusItem todayRuntime: VBusItem { bind: Utils.path(bindPrefix, "/TodayRuntime") }
- property VBusItem manualTimer: VBusItem { bind: Utils.path(bindPrefix, "/ManualStartTimer") }
- property VBusItem autoStart: VBusItem { bind: Utils.path(settingsBindPrefix, "/AutoStartEnabled") }
-
- property bool errors: ! state.valid || state.value == 10
-
- property VBusItem externalOverrideItem: VBusItem { bind: Utils.path(bindPrefix, "/ExternalOverride") }
- property bool externalOverride: externalOverrideItem.valid && externalOverrideItem.value == 1 && ! errors
- property VBusItem runningState: VBusItem { bind: Utils.path(bindPrefix, "/GeneratorRunningState") }
-
- VBusItem { id: showGaugesItem; bind: Utils.path(guiModsPrefix, "/ShowGauges") }
- property bool showGauges: showGaugesItem.valid ? showGaugesItem.value === 1 ? true : false : false
- property bool editMode: autoRunTile.editMode || manualTile.editMode
-
- VBusItem { id: serviceInterval; bind: Utils.path(settingsBindPrefix, "/ServiceInterval") }
- VBusItem { id: serviceCounterItem; bind: Utils.path(bindPrefix, "/ServiceCounter") }
- property bool showServiceInfo: serviceCounterItem.valid && serviceInterval.valid && serviceInterval.value > 0
- property bool serviceOverdue: showServiceInfo && serviceCounterItem.value < 0
-
-//////// add to display AC input ignored
- VBusItem { id: ignoreAcInput1; bind: Utils.path(sys.vebusPrefix, "/Ac/State/IgnoreAcIn1") }
- VBusItem { id: ignoreAcInput2; bind: Utils.path(sys.vebusPrefix, "/Ac/State/IgnoreAcIn2") }
- VBusItem { id: acActiveInput; bind: Utils.path(sys.vebusPrefix, "/Ac/ActiveIn/ActiveInput") }
- VBusItem { id: ac1source; bind: Utils.path("com.victronenergy.settings", "/Settings/SystemSetup/AcInput1") }
- VBusItem { id: ac2source; bind: Utils.path("com.victronenergy.settings", "/Settings/SystemSetup/AcInput2") }
-
- title: qsTr("Generator")
-
- property bool autoStartSelected: false
-
- Component.onCompleted:
- {
- setFocusManual ()
- }
-
- Keys.forwardTo: [keyHandler]
- Item
- {
- id: keyHandler
- Keys.onUpPressed:
- {
- setFocusAuto ()
- event.accepted = true
- }
- Keys.onDownPressed:
- {
- setFocusManual ()
- event.accepted = true
- }
- // prevents page changes while timers are running
- //// Keys.onReturnPressed: event.accepted = manualTile.startCountdown || autoRunTile.startCountdown
- //// Keys.onEscapePressed: event.accepted = manualTile.startCountdown || autoRunTile.startCountdown
- }
-
- function setFocusManual ()
- {
- autoStartSelected = false
- }
-
- function setFocusAuto ()
- {
- autoStartSelected = true
- }
-
- function formatTime (time)
- {
- if (time >= 3600)
- return (time / 3600).toFixed(0) + " h"
- else
- return (time / 60).toFixed(0) + " m"
- }
-
- function stateDescription()
- {
- if (!state.valid)
- return qsTr ("")
- else if (state.value === 10)
- {
- switch(error.value)
- {
- case 1:
- return qsTr("Error: Remote switch control disabled")
- case 2:
- return qsTr("Error: Generator in fault condition")
- case 3:
- return qsTr("Error: Generator not detected at AC input")
- default:
- return qsTr("Error")
- }
- }
- else
- {
- var condition = ""
- var running = true
- var manual = false
- switch (runningBy.value)
- {
- case 0: // stopped
- condition = ""
- running = false
- break;;
- case 1:
- manual = true
- condition = ""
- break;;
- case 2:
- condition = qsTr("Test run")
- break;;
- case 3:
- condition = qsTr("Loss of communication")
- break;;
- case 4:
- condition = qsTr("SOC")
- break;;
- case 5:
- condition = qsTr("AC load")
- break;;
- case 6:
- condition = qsTr("Battery current")
- break;;
- case 7:
- condition = qsTr("Battery voltage")
- break;;
- case 8:
- condition = qsTr("Inverter temperature")
- break;;
- case 9:
- condition = qsTr("Inverter overload")
- break;;
- default:
- condition = qsTr("???")
- break;;
- }
-
- if (externalOverride)
- {
- if (running && ! manual)
- return qsTr ("auto pending: ") + condition
- else
- return " "
- }
- else if (manual)
- {
- if (manualTimer.valid && manualTimer.value > 0)
- return qsTr("Timed run")
- else
- return qsTr("Manual run")
- }
- else if (running)
- return qsTr ("auto run: ") + condition
- else
- return " "
- }
- }
-
- function getNextTestRun()
- {
- if ( ! root.state.valid)
- return ""
- if (!nextTestRun.value)
- return qsTr("No test run programmed")
-
- var todayDate = new Date()
- var nextDate = new Date(nextTestRun.value * 1000)
- var nextDateEnd = new Date(nextDate.getTime())
- var message = ""
- // blank "next run" if test run is active
- if (runningBy.value == 2)
- return " "
- else if (todayDate.getDate() == nextDate.getDate() && todayDate.getMonth() == nextDate.getMonth())
- {
- message = qsTr("Next test run today %1").arg(
- Qt.formatDateTime(nextDate, "hh:mm").toString())
- }
- else
- {
- message = qsTr("Next test run on %1").arg(
- Qt.formatDateTime(nextDate, "dd/MM/yyyy").toString())
- nextDateEnd.setSeconds(testRunDuration.value) }
-
- if (skipTestRun.value === 1)
- message += qsTr(" \(skipped\)")
-
- return message
- }
-
- Tile {
- id: imageTile
- width: 180
- height: 136
- MbIcon {
- id: generator
- iconId: icon
- anchors.centerIn: parent
- }
- anchors { top: parent.top; left: parent.left }
- values: [
- // spacer
- TileText {
- width: imageTile.width - 5
- text: " "
- font.pixelSize: 62
- },
- TileText {
- width: imageTile.width - 5
- text: runningState.valid ? runningState.value == "R" ? "Running " : runningState.value == "S" ? "Stopped " : "" : ""
- }
- ]
- }
-
- Tile {
- id: statusTile
- height: imageTile.height
- color: "#4789d0"
- anchors { top: parent.top; left: imageTile.right; right: root.right }
- title: qsTr("STATUS")
- values: [
- TileText
- {
- width: statusTile.width - 5
- color: externalOverride ? "yellow" : "white"
- text:
- {
- var runPrefix
- var message
- if ( ! root.state.valid)
- return qsTr ("Generator not connected")
- else if (root.state.value === 2)
- runPrefix = qsTr("Warming up for ")
- else
- runPrefix = qsTr ("Running for ")
- if (!root.state.valid)
- message = ""
- else if (externalOverride)
- message = qsTr("External Override - stopped")
- else if (root.state.value === 3)
- message = qsTr("Cool-down")
- else if (root.state.value === 4)
- message = qsTr("Stopping")
- else if (runningBy.value == 0)
- message = qsTr ("Stopped")
- else if ( ! runningTime.valid)
- message = runPrefix + "??"
- else
- {
- message = runPrefix + formatTime (runningTime.value)
- if (manualTimer.valid && manualTimer.value > 0)
- message += qsTr (" ends in ") + formatTime (manualTimer.value)
- }
- return message
- }
- },
- Rectangle
- {
- width: parent.width
- height: 3
- color: "transparent"
- },
- TileTextMultiLine
- {
- text: stateDescription()
- width: statusTile.width - 5
- },
- Rectangle
- {
- width: parent.width
- height: 3
- color: "transparent"
- },
- TileText
- {
- text: qsTr("\nQuiet hours");
- width: statusTile.width - 5
- font.bold: runningBy.valid && runningBy.value != 0
- color: font.bold ? "yellow" : "white"
- visible: quietHours.value === 1
- },
- Rectangle
- {
- width: parent.width
- height: 3
- color: "transparent"
- },
- TileTextMultiLine
- {
- width: statusTile.width - 5
- text: getNextTestRun()
- }
- ]
- }
-
- Tile {
- id: acInTile
- title: qsTr("GENERATOR POWER")
- width: 150
- height: 136
- color: "#82acde"
- anchors { top: imageTile.bottom; left: parent.left }
- visible: showAcIn
- values:
- [
- OverviewAcValuesEnhanced
- {
- connection: sys.genset
- visible: sys.genset.power.valid
- },
- TileText
- {
- width: acInTile.width - 5
- text:
- {
- if (ac1source.valid && ac1source.value == 2)
- {
- if (ignoreAcInput1.valid && ignoreAcInput1.value == 1)
- return qsTr ("\nAC In Ignored\nduring\ngenerator\nstart / stop")
- else
- return ""
- }
- else if (ac2source.valid && ac2source.value == 2)
- {
- if (ignoreAcInput2.valid && ignoreAcInput2.value == 1)
- return qsTr ("\nAC In Ignored\nduring\ngenerator\nstart / stop")
- else
- return ""
- }
- else
- return qsTr ("\nAC In\nis not\ngenerator")
- }
- visible: !sys.genset.power.valid
- }
- ]
-////// add power bar graph
- PowerGauge
- {
- id: acInBar
- width: parent.width
- height: 12
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.genset
- useInputCurrentLimit: true
- maxForwardPowerParameter: ""
- maxReversePowerParameter: ""
- visible: showGauges && sys.genset.power.valid
- }
- }
-
-//////// added to show alternator in place of AC generator
- Tile {
- id: alternatorTile
- title: qsTr("ALTERNATOR POWER")
- color: "#157894"
- anchors.fill: acInTile
- visible: showAlternator
- values:
- [
- TileText
- {
- text: EnhFmt.formatVBusItem (sys.alternator.power, "W")
- font.pixelSize: 22
- }
- ]
-////// add power bar graph
- PowerGauge
- {
- id: alternatorGauge
- width: parent.width
- height: 12
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.alternator
- maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/MaxAlternatorPower"
- visible: showGauges && showAlternator
- }
- }
-
- Tile {
- id: runTimeTile
- title: qsTr("RUN TIMES")
- width: 140
- anchors { top: acInTile.top; bottom: parent.bottom; left: acInTile.right }
- values: [
- TileText
- {
- width: runTimeTile.width - 5
- text: qsTr ("Today")
- },
- TileText {
- width: runTimeTile.width - 5
- text: todayRuntime.valid ? formatTime (todayRuntime.value) : "--"
- },
- Rectangle
- {
- width: parent.width
- height: 3
- color: "transparent"
- },
- TileText
- {
- width: runTimeTile.width - 5
- text: qsTr ("Accumulated")
- },
- TileText
- {
- width: runTimeTile.width - 5
- text:
- {
- if ( ! totalAcummulatedTime.valid)
- return "--"
- else if (totalAccumulatedTimeOffset.valid )
- return formatTime (totalAcummulatedTime.value - totalAccumulatedTimeOffset.value)
- else
- return formatTime (totalAcummulatedTime.value)
- }
- },
- Rectangle
- {
- width: parent.width
- height: 3
- color: "transparent"
- },
- TileText
- {
- width: runTimeTile.width - 5
- visible: showServiceInfo
- color: serviceOverdue ? "red" : "white"
- text: serviceOverdue ? qsTr ("Service OVERDUE") : qsTr ("Service in")
- },
- TileText
- {
- width: runTimeTile.width - 5
- visible: showServiceInfo
- color: serviceOverdue ? "red" : "white"
- text: formatTime (Math.abs (serviceCounterItem.value))
- }
- ]
- }
-
- TileAutoRunEnhanced
- {
- id: autoRunTile
- bindPrefix: root.bindPrefix
- focus: root.active && autoStartSelected
- connected: state.valid
- tileHeight: acInTile.height / 2
- anchors {
- bottom: parent.bottom; bottomMargin: tileHeight
- left: runTimeTile.right
- right: parent.right
- }
- }
-
- TileManualStartEnhanced
- {
- id: manualTile
- bindPrefix: root.bindPrefix
- focus: root.active && ! autoStartSelected
- connected: state.valid
- tileHeight: acInTile.height / 2
- anchors {
- bottom: parent.bottom
- left: runTimeTile.right
- right: parent.right
- }
- }
-
- // mouse areas must be AFTER their associated objects so those objects can catch mouse events
- // rejected by these areas
- // mouse targets need to be disabled while changes are pending
- MouseArea {
- id: autoRunTarget
- anchors.fill: autoRunTile
- enabled: root.active && ! editMode
- onPressed:
- {
- if ( ! root.autoStartSelected )
- {
- setFocusAuto ()
- mouse.accepted = true
- }
- else
- {
- mouse.accepted = false
- }
- }
- }
- MouseArea {
- id: manualStartTarget
- anchors.fill: manualTile
- enabled: root.active && ! editMode
- onPressed:
- {
- if ( root.autoStartSelected )
- {
- setFocusManual ()
- mouse.accepted = true
- }
- else
- {
- mouse.accepted = false
- }
- }
- }
-}
diff --git a/FileSets/v3.50~25/PageSettingsGuiMods.qml b/FileSets/v3.50~25/PageSettingsGuiMods.qml
deleted file mode 100644
index 420a42ac..00000000
--- a/FileSets/v3.50~25/PageSettingsGuiMods.qml
+++ /dev/null
@@ -1,290 +0,0 @@
-/////// new menu for all Gui Mods
-
-import QtQuick 1.1
-import "utils.js" as Utils
-import com.victron.velib 1.0
-
-MbPage {
- id: root
- title: qsTr("Gui Mods")
- property string bindPrefixGuiMods: "com.victronenergy.settings/Settings/GuiMods"
- property string bindPrefix: "com.victronenergy.settings/Settings/Gui"
- property VBusItem systemScaleItem: VBusItem { bind: "com.victronenergy.settings/Settings/System/Units/Temperature" }
-
- property bool showFlowParams: flowOverview.item.valid && flowOverview.item.value >= 1
- property bool showComplexParams: flowOverview.item.valid && flowOverview.item.value >= 2
- property bool showAcCoupledParams: flowOverview.item.valid && flowOverview.item.value == 3
-
- model: VisibleItemModel
- {
- MbSwitch
- {
- id: showTileOverview
- bind: Utils.path (bindPrefixGuiMods, "/ShowTileOverview")
- name: qsTr ("Show Tile Overview")
- writeAccessLevel: User.AccessUser
- }
-
- MbSwitch
- {
- id: moveSettings
- bind: Utils.path (bindPrefixGuiMods, "/MoveSettings")
- name: qsTr ("Move Settings to top of Device List")
- writeAccessLevel: User.AccessUser
- }
-
- MbSwitch {
- id: relayOverview
- bind: Utils.path (bindPrefixGuiMods, "/ShowRelayOverview")
- name: qsTr ("Show Relay overview")
- writeAccessLevel: User.AccessUser
- }
- MbSwitch {
- id: tanksTempsOverview
- bind: Utils.path (bindPrefixGuiMods, "/ShowTanksTempsDigIn")
- name: qsTr ("Show Tanks, Temps, Digital Input overview")
- writeAccessLevel: User.AccessUser
- }
-
- MbSwitch
- {
- id: useEnhGeneratorOverview
- bind: Utils.path (bindPrefixGuiMods, "/UseEnhancedGeneratorOverview")
- name: qsTr ("Use Enhanced Generator Overview")
- writeAccessLevel: User.AccessUser
- }
-
- // duplicate mobile overview on/off here for convenience
- MbSwitch {
- id: mobileOverview
- bind: Utils.path (bindPrefix, "/MobileOverview")
- name: qsTr ("Show boat & motorhome overview")
- writeAccessLevel: User.AccessUser
- }
- MbSwitch
- {
- id: useEnhMobileOverview
- bind: Utils.path (bindPrefixGuiMods, "/UseEnhancedMobileOverview")
- name: qsTr ("Use Enhanced Mobile Overview")
- // When enabled set Enhanced OverviewMobile as default overview
- onClicked:
- {
- if (!checked)
- {
- // also enable Mobile Overview when turning on use enhanced Mobile Overview
- showMobileOverview.setValue (1)
- }
- }
- VBusItem { id: showMobileOverview; bind: Utils.path (bindPrefix, "/MobileOverview") }
- writeAccessLevel: User.AccessUser
- }
- MbItemOptions
- {
- id: flowOverview
- description: qsTr("Flow overview")
- bind: Utils.path (bindPrefixGuiMods, "/FlowOverview")
- possibleValues:
- [
- MbOption {description: qsTr("Victron stock"); value: 0},
- MbOption {description: qsTr("GuiMods simple"); value: 1},
- MbOption {description: qsTr("GuiMods DC Coupled"); value: 2},
- MbOption {description: qsTr("GuiMods AC Coupled"); value: 3}
- ]
- }
-
- MbSwitch
- {
- id: combineLoads
- bind: Utils.path (bindPrefixGuiMods, "/EnhancedFlowCombineLoads")
- name: qsTr ("Combine AC input/ouput loads")
- show: root.showAcCoupledParams
- writeAccessLevel: User.AccessInstaller
- }
- MbSwitch
- {
- id: showLoadsOnInput
- bind: Utils.path (bindPrefixGuiMods, "/ShowEnhancedFlowLoadsOnInput")
- name: qsTr ("Show Loads On Input")
- show: root.showAcCoupledParams && ! combineLoads.checked
- writeAccessLevel: User.AccessInstaller
- }
-
- MbSwitch
- {
- id: showTanks
- bind: Utils.path (bindPrefixGuiMods, "/ShowEnhancedFlowOverviewTanks")
- name: qsTr ("Show tanks on Flow Overview")
- show: root.showFlowParams
- writeAccessLevel: User.AccessUser
- }
- MbItemOptions
- {
- id: tankFormat
- description: qsTr("Tank bar format")
- bind: Utils.path (bindPrefixGuiMods, "/TankBarFormat")
- possibleValues:
- [
- MbOption {description: qsTr("%"); value: 1},
- MbOption {description: qsTr("units"); value: 2},
- MbOption {description: qsTr("% + units"); value: 0}
- ]
- }
- MbSwitch
- {
- id: showTemps
- bind: Utils.path (bindPrefixGuiMods, "/ShowEnhancedFlowOverviewTemps")
- name: qsTr ("Show temperatures on Flow Overview")
- show: root.showFlowParams
- writeAccessLevel: User.AccessUser
- }
- MbSwitch
- {
- id: showBatteryTemps
- bind: Utils.path (bindPrefixGuiMods, "/ShowBatteryTempOnFlows")
- name: qsTr ("Show battery temperature on Flow Overview")
- show: showTemps.item.value == 1
- writeAccessLevel: User.AccessUser
- }
- MbSwitch
- {
- id: shortenTankNames
- bind: Utils.path (bindPrefixGuiMods, "/ShortenTankNames")
- name: qsTr ("Shorten tank names")
- writeAccessLevel: User.AccessUser
- }
- MbEditBox {
- id: dcSystemName
- description: qsTr("DC System tile name")
- item.bind: Utils.path (bindPrefixGuiMods, "/CustomDcSystemName")
- maximumLength: 32
- enableSpaceBar: true
- }
-
- MbSwitch
- {
- id: replaceInactiveAcIn
- bind: Utils.path (bindPrefixGuiMods, "/ReplaceInactiveAcIn")
- name: qsTr ("Replace AC in if inactive")
- writeAccessLevel: User.AccessUser
- }
-
- MbSpinBox {
- description: qsTr ("AC Input Limit Preset 1")
- item
- {
- bind: Utils.path (bindPrefixGuiMods, "/AcCurrentLimit/Preset1")
- unit: "A"
- decimals: 0
- step: 1
- min: 0
- max: 999
- }
- writeAccessLevel: User.AccessUser
- }
-
- MbSpinBox {
- description: qsTr ("AC Input Limit Preset 2")
- item
- {
- bind: Utils.path (bindPrefixGuiMods, "/AcCurrentLimit/Preset2")
- unit: "A"
- decimals: 0
- step: 1
- min: 0
- max: 999
- }
- writeAccessLevel: User.AccessUser
- }
-
- MbSpinBox {
- description: qsTr ("AC Input Limit Preset 3")
- item
- {
- bind: Utils.path (bindPrefixGuiMods, "/AcCurrentLimit/Preset3")
- unit: "A"
- decimals: 0
- step: 1
- min: 0
- max: 999
- }
- writeAccessLevel: User.AccessUser
- }
-
- MbSpinBox {
- description: qsTr ("AC Input Limit Preset 4")
- item
- {
- bind: Utils.path (bindPrefixGuiMods, "/AcCurrentLimit/Preset4")
- unit: "A"
- decimals: 0
- step: 1
- min: 0
- max: 999
- }
- writeAccessLevel: User.AccessUser
- }
-
- MbItemOptions
- {
- id: tempScale
- description: qsTr ("Temperature scale")
- bind: Utils.path (bindPrefixGuiMods, "/TemperatureScale")
- show: ! systemScaleItem.valid
- possibleValues:
- [
- MbOption { description: "°C"; value: 1 },
- MbOption { description: "°F"; value: 2 },
- MbOption { description: qsTr("both °C & °F"); value: 0 }
- ]
- writeAccessLevel: User.AccessUser
- }
-
- MbSpinBox {
- description: qsTr ("Watt / Kilowatt threshold")
- item
- {
- bind: Utils.path (bindPrefixGuiMods, "/KilowattThreshold")
- unit: "W"
- decimals: 0
- step: 100
- min: 1000
- max: 10000
- }
- writeAccessLevel: User.AccessUser
- }
-
- MbItemOptions
- {
- id: timeFormat
- description: qsTr ("Time format")
- bind: Utils.path (bindPrefixGuiMods, "/TimeFormat")
- possibleValues:
- [
- MbOption { description: qsTr("24 hour"); value: 1 },
- MbOption { description: qsTr("12 hour AM/PM"); value: 2 },
- MbOption { description: qsTr("don't show time"); value: 0 }
- ]
- writeAccessLevel: User.AccessUser
- }
- MbItemOptions
- {
- id: inactiveFlowTiles
- description: qsTr ("Inactive Tiles on Flow Overview")
- bind: Utils.path (bindPrefixGuiMods, "/ShowInactiveFlowTiles")
- show: root.showFlowParams
- possibleValues:
- [
- MbOption { description: qsTr("Show Dimmed"); value: 1 },
- MbOption { description: qsTr("Show Full"); value: 2 },
- MbOption { description: qsTr("Hide"); value: 0 }
- ]
- writeAccessLevel: User.AccessUser
- }
- MbSubMenu
- {
- description: qsTr("Power Gauges")
- subpage: Component { PageSettingsGuiModsGauges {} }
- show: root.showFlowParams
- }
- }
-}
diff --git a/FileSets/v3.50~25/TileDigIn.qml b/FileSets/v3.50~25/TileDigIn.qml
deleted file mode 100644
index 492b8b2d..00000000
--- a/FileSets/v3.50~25/TileDigIn.qml
+++ /dev/null
@@ -1,133 +0,0 @@
-// New for GuiMods to display digital inputs
-// based on TileTank.qml
-
-import QtQuick 1.1
-import "utils.js" as Utils
-import "tanksensor.js" as TankSensor
-
-Tile {
- id: root
-
- property string bindPrefix: serviceName
- property VBusItem nameItem: VBusItem { bind: Utils.path(bindPrefix, "/CustomName") }
- property VBusItem deviceItem: VBusItem { bind: Utils.path(bindPrefix, "/DeviceInstance") }
- property VBusItem aggregateItem: VBusItem { bind: Utils.path(bindPrefix, "/Aggregate") }
- property string digInName: nameItem.valid && nameItem.value != "" ? nameItem.value : getType (type)
- property VBusItem typeItem: VBusItem { bind: Utils.path(bindPrefix, "/Type") }
- property VBusItem stateItem: VBusItem { bind: Utils.path(bindPrefix, "/State") }
- property bool isPulseCounter: aggregateItem.valid
- // pulse counter doesn't have /Type so fill it in here
- property int type: isPulseCounter ? 1 : typeItem.valid ? typeItem.value : 0
-
- property variant bkgdColors: [ "#b3b3b3", "#4aa3df", "#1abc9c", "#F39C12", "#95a5a6", "#95a5a6","#dcc6e0", "#f1a9a0", "#7f8c8d", "#ebbc3a" ]
- property color bkgdColor: type > 0 && type < 10 ? bkgdColors [type] : "#b3b3b3"
- property variant units: ["m3", "L", "gal", "gal"]
-
-
- function getType(type)
- {
- switch (type)
- {
- case 0:
- return qsTr("Disabled")
- case 1:
- return qsTr("Pulse meter")
- case 2:
- return qsTr("Door alarm")
- case 3:
- return qsTr("Bilge pump")
- case 4:
- return qsTr("Bilge alarm")
- case 5:
- return qsTr("Burglar alarm")
- case 6:
- return qsTr("Smoke alarm")
- case 7:
- return qsTr("Fire alarm")
- case 8:
- return qsTr("CO2 alarm")
- case 9:
- return qsTr("Generator")
- case 10:
- return qsTr("Generic I/O")
-//// added for ExtTransferSwitch package
- case 11:
- return qsTr("Touch enable")
- case 12:
- return qsTr("Transfer switch")
- default:
- return "Unknown"
- }
- }
-
- function getState(st)
- {
- switch (st)
- {
- case 0:
- return qsTr("Low")
- case 1:
- return qsTr("High")
- case 2:
- return qsTr("Off")
- case 3:
- return qsTr("On")
- case 4:
- return qsTr("No")
- case 5:
- return qsTr("Yes")
- case 6:
- return qsTr("Open")
- case 7:
- return qsTr("Closed")
- case 8:
- return qsTr("Ok")
- case 9:
- return qsTr("Alarm")
- case 10:
- return qsTr("Running")
- case 11:
- return qsTr("Stopped")
-//// added for ExtTransferSwitch package
- case 12:
- return qsTr("On Generator")
- case 13:
- return qsTr("On Grid")
- default:
- return qsTr("Unknown")
- }
-
- }
-
- title: digInName + " (In " + (deviceItem.valid ? (deviceItem.value.toString ()) : "?") + ")"
-
- color: bkgdColor
-
- VBusItem
- {
- id: unitItem
- bind: Utils.path("com.victronenergy.settings/Settings/System/VolumeUnit")
- }
-
- values: Item
- {
- width: root.width - 10
- height: 12
- TileText
- {
- width: root.width
- text:
- {
- if (isPulseCounter)
- return aggregateItem.value.toString() + (unitItem.valid ? units[unitItem.value] : "??")
- else
- return stateItem.valid ? getState (stateItem.value) : "??"
- }
- horizontalAlignment: Text.AlignHCenter
- anchors
- {
- horizontalCenter: parent.horizontalCenter
- }
- }
- }
-}
diff --git a/FileSets/v3.50~5/COMPLETE b/FileSets/v3.50~5/COMPLETE
deleted file mode 100644
index e69de29b..00000000
diff --git a/FileSets/v3.50~5/DetailAcInput.qml b/FileSets/v3.50~5/DetailAcInput.qml
deleted file mode 120000
index cadafa4e..00000000
--- a/FileSets/v3.50~5/DetailAcInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/DetailInverter.qml b/FileSets/v3.50~5/DetailInverter.qml
deleted file mode 120000
index 8a637d73..00000000
--- a/FileSets/v3.50~5/DetailInverter.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/DetailLoadsCombined.qml b/FileSets/v3.50~5/DetailLoadsCombined.qml
deleted file mode 120000
index 5775b2f4..00000000
--- a/FileSets/v3.50~5/DetailLoadsCombined.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/DetailLoadsOnInput.qml b/FileSets/v3.50~5/DetailLoadsOnInput.qml
deleted file mode 120000
index 8486816b..00000000
--- a/FileSets/v3.50~5/DetailLoadsOnInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/DetailLoadsOnOutput.qml b/FileSets/v3.50~5/DetailLoadsOnOutput.qml
deleted file mode 120000
index 5aba1991..00000000
--- a/FileSets/v3.50~5/DetailLoadsOnOutput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/HubData.qml b/FileSets/v3.50~5/HubData.qml
deleted file mode 120000
index c7e782ee..00000000
--- a/FileSets/v3.50~5/HubData.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/ObjectAcConnection.qml b/FileSets/v3.50~5/ObjectAcConnection.qml
deleted file mode 120000
index 2ca36f5e..00000000
--- a/FileSets/v3.50~5/ObjectAcConnection.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewAcValuesEnhanced.qml b/FileSets/v3.50~5/OverviewAcValuesEnhanced.qml
deleted file mode 120000
index 2fcae9eb..00000000
--- a/FileSets/v3.50~5/OverviewAcValuesEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewFlowComplex.qml b/FileSets/v3.50~5/OverviewFlowComplex.qml
deleted file mode 120000
index b8f9a5f4..00000000
--- a/FileSets/v3.50~5/OverviewFlowComplex.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewGeneratorEnhanced.qml b/FileSets/v3.50~5/OverviewGeneratorEnhanced.qml
deleted file mode 120000
index 51b929eb..00000000
--- a/FileSets/v3.50~5/OverviewGeneratorEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/OverviewGeneratorEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.50~5/OverviewGeneratorRelayEnhanced.qml
deleted file mode 120000
index 65979936..00000000
--- a/FileSets/v3.50~5/OverviewGeneratorRelayEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewGridParallel.qml b/FileSets/v3.50~5/OverviewGridParallel.qml
deleted file mode 120000
index 7af3834a..00000000
--- a/FileSets/v3.50~5/OverviewGridParallel.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewHub.qml b/FileSets/v3.50~5/OverviewHub.qml
deleted file mode 120000
index 88267085..00000000
--- a/FileSets/v3.50~5/OverviewHub.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewHubEnhanced.qml b/FileSets/v3.50~5/OverviewHubEnhanced.qml
deleted file mode 120000
index 2e238e43..00000000
--- a/FileSets/v3.50~5/OverviewHubEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/OverviewMobileEnhanced.qml b/FileSets/v3.50~5/OverviewMobileEnhanced.qml
deleted file mode 100644
index 175a4bac..00000000
--- a/FileSets/v3.50~5/OverviewMobileEnhanced.qml
+++ /dev/null
@@ -1,989 +0,0 @@
-// GuiMods Enhancements to OverviewMobile screen
-
-// Removed logo and added AC INPUT and SYSTEM tiles originally displayed on other overviews
-// Added voltage, current and frequency to AC INPUT and AC LOADS tiles
-// Added source (Grid, Generator, Shore Power) to AC INPUT tile
-// Replaced to/from battery with current in DC SYSTEM tile
-// DC SYSTEM tile title now reflects direction: "DC LOADS, DC CHARGER"
-// Rearranged tiles to match a left to right signal flow : sources on left, loads on right
-// Standardized "info" tile sizes to 1 or 1.5 wide x 1 or 2 high
-// infoArea defines usable space for info tiles and all tiles are a child of infoArea
-// (makes repositioning easier than when they were in separate column objects)
-// Large text for main paremeter in each tile has been reduced in size to allow more parameters without
-// expanding tile height (30 to 22)
-// merged SYSTEM and STATUS tiles
-// removed speed from STATUS to reduce tile height
-// hide "reason" text if it's blank to save space
-// changed clock to 12-hour format
-// Capitialized battery state: "Idle", "Charging", "Discharging"
-// errors and notificaitons in SYSTEM/STATUS tile may push clock off bottom of tile
-// Tile content for items that are not present are made invisible - tile remains in place
-// that is no height adjustments when a tile provides no information
-// Adjust button widths so that pump button fits within tank column
-// Hide pump button when not enabled giving more room for tanks
-// Add temperature sensors to tanks column
-// add control of VE.Direct inverters
-
-// Includes changes to handle SeeLevel NMEA2000 tank sensor:
-// Ignore the real incoming tank dBus service because it's information changes
-// Changes in TileText.qml are also part of the TankRepeater package
-
-// Search for //////// to find changes
-
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-import "timeToGo.js" as TTG
-import "enhancedFormat.js" as EnhFmt
-
-OverviewPage {
- title: qsTr("Mobile")
- id: root
-
- property color detailColor: "#b3b3b3"
- property real touchTargetOpacity: 0.3
- property int touchArea: 40
-
- property variant sys: theSystem
- property string settingsBindPreffix: "com.victronenergy.settings"
- property string pumpBindPreffix: "com.victronenergy.pump.startstop0"
- property variant activeNotifications: NotificationCenter.notifications.filter(
- function isActive(obj) { return obj.active} )
- property string noAdjustableByDmc: qsTr("This setting is disabled when a Digital Multi Control " +
- "is connected. If it was recently disconnected execute " +
- "\"Redetect system\" that is available on the inverter menu page.")
- property string noAdjustableByBms: qsTr("This setting is disabled when a VE.Bus BMS " +
- "is connected. If it was recently disconnected execute " +
- "\"Redetect system\" that is available on the inverter menu page.")
- property string noAdjustableTextByConfig: qsTr("This setting is disabled. " +
- "Possible reasons are \"Overruled by remote\" is not enabled or " +
- "an assistant is preventing the adjustment. Please, check " +
- "the inverter configuration with VEConfigure.")
-
-//////// added to keep track of tanks and temps
- property int numberOfTemps: 0
- property int tankTempCount: tankModel.rowCount + numberOfTemps
- property real tanksTempsHeight: root.height - (pumpButton.pumpEnabled ? pumpButton.height : 0)
- property real tanksHeight: tankModel.rowCount > 0 ? tanksTempsHeight * tankModel.rowCount / tankTempCount : 0
- property real tempsHeight: tanksTempsHeight - tanksHeight
- property real minimumTankHeight: 21
- property real maxTankHeight: 80
- property real tankTileHeight: Math.min (Math.max (tanksTempsHeight / tankTempCount, minimumTankHeight), maxTankHeight)
-
- property bool compact: tankTempCount > (pumpButton.pumpEnabled ? 5 : 6)
-
- property string systemPrefix: "com.victronenergy.system"
- VBusItem { id: vebusService; bind: Utils.path(systemPrefix, "/VebusService") }
- property bool isMulti: vebusService.valid
- property string veDirectInverterService: ""
- property string inverterService: vebusService.valid ? vebusService.value : veDirectInverterService
-
- property bool isInverter: ! isMulti && veDirectInverterService != ""
- property bool hasAcInput: isMulti
- VBusItem { id: _hasAcOutSystem; bind: "com.victronenergy.settings/Settings/SystemSetup/HasAcOutSystem" }
- property bool hasAcOutSystem: _hasAcOutSystem.value === 1
-
-//////// add for system state
- property bool hasSystemState: _systemState.valid
-
-//////// add for SYSTEM tile and voltage, power and frequency values
- property VBusItem _systemState: VBusItem { bind: Utils.path(systemPrefix, "/SystemState/State") }
-//////// add for PV CHARGER voltage and current
- property string pvChargerPrefix: ""
- property int numberOfPvChargers: 0
-
-
- //////// standard tile sizes
- //////// positions are left, center, right and top, center, bottom of infoArea
- property int tankWidth: 130
-
- property int upperTileHeight: 185
- property int acTileHeight: height - upperTileHeight
-
- property int infoWidth: width - tankWidth
- property int infoWidth3Column: infoWidth / 3
- property int infoWidth2Column: infoWidth / 2
-
-//////// add for PV Charger voltage and current
- VBusItem { id: pvNrTrackers; bind: Utils.path(pvChargerPrefix, "/NrOfTrackers") }
- property bool singleTracker: ! pvNrTrackers.valid || pvNrTrackers.value == 1
- property bool showPvVI: numberOfPvChargers == 1 && singleTracker
- VBusItem { id: pvPower; bind: Utils.path(pvChargerPrefix, "/Yield/Power") }
- VBusItem { id: pvVoltage; bind: Utils.path(pvChargerPrefix, singleTracker ? "/Pv/V" : "/Pv/0/V") }
-
-//////// add for inverter mode in STATUS
- VBusItem { id: inverterMode; bind: Utils.path(inverterService, "/Mode") }
-
-//////// add for gauges
- VBusItem { id: showGaugesItem; bind: Utils.path(guiModsPrefix, "/ShowGauges") }
- property bool showGauges: showGaugesItem.valid ? showGaugesItem.value === 1 ? true : false : false
-
-//////// added to control time display
- property string guiModsPrefix: "com.victronenergy.settings/Settings/GuiMods"
- VBusItem { id: timeFormatItem; bind: Utils.path(guiModsPrefix, "/TimeFormat") }
- property string timeFormat: getTimeFormat ()
-
- function getTimeFormat ()
- {
- if (!timeFormatItem.valid || timeFormatItem.value === 0)
- return ""
- else if (timeFormatItem.value === 2)
- return "h:mm ap"
- else
- return "hh:mm"
- }
-
- Component.onCompleted: { discoverServices(); showHelp () }
-
- // define usable space for tiles but don't show anything
- Rectangle {
- id: infoArea
- visible: false
- anchors {
- left: parent.left
- right: tanksColum.left
- top: parent.top;
- bottom: parent.bottom;
- }
- }
-
-//////// change time to selectable 12/24 hour format
- Timer {
- id: wallClock
- running: timeFormat != ""
- repeat: true
- interval: 1000
- triggeredOnStart: true
- onTriggered: time = Qt.formatDateTime(new Date(), timeFormat)
- property string time
- }
-
- VBusItem { id: systemName; bind: Utils.path(settingsBindPreffix, "/Settings/SystemSetup/SystemName") }
-
-//////// copied SYSTEM from OverviewTiles.qml & combined SYSTEM and STATUS tiles
- Tile {
- title: qsTr("STATUS")
- id: statusTile
- anchors { left: parent.left; top: parent.top }
- width: root.infoWidth3Column
- height: root.upperTileHeight - inverterTile.height
- color: "#4789d0"
-
-//////// relorder to give priority to errors
- values: [
- TileText {
- text: systemName.valid && systemName.value !== "" ? systemName.value : sys.systemType.valid ? sys.systemType.value.toUpperCase() : ""
- font.pixelSize: 16
- wrapMode: Text.WordWrap
- width: statusTile.width - 5
- },
- TileText {
- text: wallClock.running ? wallClock.time : ""
- font.pixelSize: 15
- },
-//////// combine SystemReason with notifications
- MarqueeEnhanced {
- text:
- {
- if (activeNotifications.length === 0)
- return systemReasonMessage.text
- else
- return notificationText() + " || " + systemReasonMessage.text
- }
- width: statusTile.width
- textHorizontalAlignment: Text.AlignHCenter
- interval: 100
- SystemReasonMessage {
- id: systemReasonMessage
- }
- },
- TileText {
- property VeQuickItem gpsService: VeQuickItem { uid: "dbus/com.victronenergy.system/GpsService" }
- property VeQuickItem speed: VeQuickItem { uid: Utils.path("dbus/", gpsService.value, "/Speed") }
- property VeQuickItem speedUnit: VeQuickItem { uid: "dbus/com.victronenergy.settings/Settings/Gps/SpeedUnit" }
-
- text: speed.value === undefined ? "" : getValue()
- visible: speed.value !== undefined && speedUnit.value !== undefined
-
- function getValue()
- {
- if (speed.value < 0.5) // blank speed if less than about 1 MPH
- return " "
- if (speedUnit.value === "km/h")
- return (speed.value * 3.6).toFixed(1) + speedUnit.value
- if (speedUnit.value === "mph")
- return (speed.value * 2.236936).toFixed(1) + speedUnit.value
- if (speedUnit.value === "kt")
- return (speed.value * (3600/1852)).toFixed(1) + speedUnit.value
- return speed.value.toFixed(2) + "m/s"
- }
- }
- ]
- } // end Tile STATUS
- Tile
- {
- title: qsTr("INVERTER")
- id: inverterTile
- anchors { left: parent.left; top: statusTile.bottom }
- width: root.infoWidth3Column
- height: 62
- color: "#4789d0"
-
- values: [
- TileText
- {
- text: inverterMode.valid ? inverterMode.text : "--"
- },
- TileText {
- text: qsTr(systemState.text)
-
- SystemState {
- id: systemState
- bind: hasSystemState?Utils.path(systemPrefix, "/SystemState/State"):Utils.path(inverterService, "/State")
- }
- }
- ]
-////// add power bar graph
- PowerGaugeMulti
- {
- id: multiBar
- width: parent.width - 10
- height: 8
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- inverterService: root.inverterService
- visible: showGauges
- }
- DetailTarget { id: multiTarget; detailsPage: "DetailInverter.qml" }
- } // end Tile INVERTER
-
- Tile {
- title: qsTr("BATTERY")
- id: batteryTile
- anchors { horizontalCenter: infoArea.horizontalCenter; top: infoArea.top }
- width: root.infoWidth3Column
- height: root.upperTileHeight
-
- values: [
- TileText // spacer
- {
- text: ""
- font.pixelSize: 6
- },
- TileText {
- text: sys.battery.soc.absFormat(0)
- font.pixelSize: 22
- //////// remove height (for consistency with other tiles)
- },
- TileText {
- text: {
- if (!sys.battery.state.valid)
- return "---"
- switch(sys.battery.state.value) {
- //////// change - capitalized words look better
- case sys.batteryStateIdle: return qsTr("Idle")
- case sys.batteryStateCharging : return qsTr("Charging")
- case sys.batteryStateDischarging : return qsTr("Discharging")
- }
- }
- },
- TileText {
-//////// change to show negative for battery drain
- text: sys.battery.power.text
- font.pixelSize: 18
- },
- TileText {
- text: sys.battery.voltage.format(2)
- },
- TileText {
- text: sys.battery.current.format(1)
- },
- TileText {
- text: qsTr("Remaining:")
- visible: timeToGo.valid
- },
- TileText {
- id: timeToGoText
- text: timeToGo.valid ? TTG.formatTimeToGo (timeToGo) : " "
- visible: timeToGo.valid
-
- VBusItem {
- id: timeToGo
- bind: Utils.path("com.victronenergy.system","/Dc/Battery/TimeToGo")
- }
- }
- ]
-////// add battery current bar graph
- PowerGaugeBattery
- {
- id: batteryBar
- width: parent.width - 10
- height: 8
- endLabelFontSize: 14
- endLabelBackgroundColor: batteryTile.color
- anchors
- {
- top: parent.top; topMargin: 22
- horizontalCenter: parent.horizontalCenter
- }
- visible: showGauges
- }
- DetailTarget { id: batteryTarget; detailsPage: "DetailBattery.qml" }
- } // end Tile BATTERY
-
- VBusItem { id: dcSystemNameItem; bind: Utils.path(settingsBindPreffix, "/Settings/GuiMods/CustomDcSystemName") }
-
- Tile {
- title: dcSystemNameItem.valid && dcSystemNameItem.value != "" ? dcSystemNameItem.value : qsTr ("DC SYSTEM")
- id: dcSystem
- anchors { right: infoArea.right; bottom: infoArea.bottom; bottomMargin: root.acTileHeight }
- width: root.infoWidth3Column
- height: (root.upperTileHeight / 2) - 5
- color: "#16a085"
- values: [
- TileText { // spacer
- font.pixelSize: 6
- text: ""
- },
- TileText {
- font.pixelSize: 22
- text: EnhFmt.formatVBusItem (sys.dcSystem.power)
- visible: sys.dcSystem.power.valid
- },
- ////// replace to/from battery with current
- TileText {
- text: !sys.dcSystem.power.valid ? "---" :
- EnhFmt.formatValue (sys.dcSystem.power.value / sys.battery.voltage.value, "A")
- visible: sys.dcSystem.power.valid
- }
- ]
- PowerGauge
- {
- id: dcSystemGauge
- width: parent.width - 10
- height: 8
- anchors
- {
- top: parent.top; topMargin: 22
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.dcSystem
- endLabelFontSize: 12
- endLabelBackgroundColor: dcSystem.color
- maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/DcSystemMaxLoad"
- maxReversePowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/DcSystemMaxCharge"
- showLabels: true
- visible: showGauges && sys.dcSystem.power.valid
- }
- DetailTarget { id: dcSystemTarget; detailsPage: "DetailDcSystem.qml" }
- } // end Tile DC SYSTEM
-
- Tile {
- id: solarTile
- title: qsTr("PV CHARGER")
- anchors { right: infoArea.right; top: infoArea.top }
- width: root.infoWidth3Column
- height: root.upperTileHeight - dcSystem.height
- color: "#2cc36b"
- values: [
- TileText {
- font.pixelSize: 22
- text: EnhFmt.formatVBusItem (sys.pvCharger.power)
- },
- //////// add voltage
- TileText {
- text:
- {
- if (showPvVI)
- return EnhFmt.formatVBusItem (pvVoltage, "V")
- else
- return ""
- }
- visible: showPvVI
- },
- //////// add current
- TileText {
- text:
- {
- if (showPvVI && pvPower.valid && pvVoltage.valid)
- {
- var voltage = pvVoltage.value
- return EnhFmt.formatValue ((pvPower.value / voltage), "A")
- }
- else
- return ""
- }
- visible: showPvVI
- }
- ]
-////// add power bar graph
- PowerGauge
- {
- id: pvChargerBar
- width: parent.width - 10
- height: 8
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.pvCharger
- maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/PvChargerMaxPower"
- visible: showGauges && sys.pvCharger.power.valid
- }
- DetailTarget { id: pvChargerTarget; detailsPage: "DetailPvCharger.qml" }
- } // end Tile PV CHARGER
-
-//////// add to display AC input ignored
- VBusItem { id: ignoreAcInput; bind: Utils.path(inverterService, "/Ac/State/IgnoreAcIn1") }
-
-//////// add AC INPUT tile
- Tile {
- id: acInputTile
- title: {
- if (isInverter)
- return qsTr ("No AC Input")
- else if (ignoreAcInput.valid && ignoreAcInput.value == 1)
- return qsTr ("AC In Ignored")
- else
- {
- switch(sys.acSource) {
- case 1: return qsTr("GRID")
- case 2: return qsTr("GENERATOR")
- case 3: return qsTr("SHORE POWER")
- default: return qsTr("AC INPUT")
- }
- }
- }
- anchors { left: infoArea.left; bottom: infoArea.bottom }
- width: root.infoWidth2Column
- height: root.acTileHeight
- color: "#82acde"
-//////// add voltage and current
- VBusItem { id: currentLimit; bind: Utils.path(inverterService, "/Ac/ActiveIn/CurrentLimit") }
- values: [
- TileText {
- visible: isMulti
- text: EnhFmt.formatVBusItem (sys.acInput.power)
- font.pixelSize: 20
-
- },
-//////// add voltage and current
- TileText {
- visible: isMulti
- text: EnhFmt.formatVBusItem (sys.acInput.voltageL1, "V") + " " + EnhFmt.formatVBusItem (sys.acInput.currentL1, "A") + " " + EnhFmt.formatVBusItem (sys.acInput.frequency, "Hz")
- },
- TileText
- {
- text: qsTr ("Limit: ") + EnhFmt.formatVBusItem (currentLimit, "A")
- visible: currentLimit.valid
- }
- ]
-////// add power bar graph
- PowerGauge
- {
- id: acInBar
- width: parent.width - 10
- height: 8
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.acInput
- useInputCurrentLimit: true
- maxForwardPowerParameter: ""
- maxReversePowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/MaxFeedInPower"
- visible: showGauges && hasAcInput
- }
- DetailTarget { id: acInputTarget; detailsPage: "DetailAcInput.qml" }
- }
-
- Tile {
- title: qsTr("AC LOADS")
- id: acLoadsTile
- anchors { right: infoArea.right; bottom: infoArea.bottom}
- width: root.infoWidth2Column
- height: root.acTileHeight
- color: "#e68e8a"
-//////// add voltage and current
- VBusItem { id: outVoltage; bind: Utils.path(inverterService, "/Ac/Out/L1/V") }
- VBusItem { id: outCurrent; bind: Utils.path(inverterService, "/Ac/Out/L1/I") }
- VBusItem { id: outFrequency; bind: Utils.path(inverterService, "/Ac/Out/L1/F") }
-
- values: [
- TileText {
- text: EnhFmt.formatVBusItem (sys.acLoad.power)
- font.pixelSize: 22
- },
-//////// add voltage and current - no frequency for VE.Direct inverter
- TileText {
- text:
- {
- var lineText = ""
- if (isMulti || isInverter)
- {
- lineText = EnhFmt.formatVBusItem (outVoltage, "V") + " " + EnhFmt.formatVBusItem (outCurrent, "A")
- if (isMulti)
- lineText += " " + EnhFmt.formatVBusItem (outFrequency, "Hz")
- }
- return lineText
- }
- }
- ]
-////// add power bar graph
- PowerGauge
- {
- id: acLoadBar
- width: parent.width - 10
- height: 8
- anchors
- {
- top: parent.top; topMargin: 20
- horizontalCenter: parent.horizontalCenter
- }
- connection: sys.acLoad
- maxForwardPowerParameter: "com.victronenergy.settings/Settings/GuiMods/GaugeLimits/AcOutputMaxPower"
- visible: showGauges && hasAcOutSystem
- }
- DetailTarget { id: acLoadsOnOutputTarget; detailsPage: "DetailLoadsOnOutput.qml" }
- }
-
- // Synchronise tank name text scroll start
- Timer {
- id: scrollTimer
- interval: 15000
- repeat: true
- running: root.active
- }
-
- ListView {
- id: tanksColum
-
- anchors {
- top: root.top
- right: root.right
- }
- height: root.tanksHeight
- width: root.tankWidth
-//////// make list flickable if more tiles than will fit completely
- interactive: root.tankTileHeight * count > (tanksColum.height + 1) ? true : false
-
- model: TankModel { id: tankModel }
- delegate: TileTankEnhanced {
- // Without an intermediate assignment this will trigger a binding loop warning.
- property variant theService: DBusServices.get(buddy.id)
- service: theService
- width: tanksColum.width
- height: root.tankTileHeight
- pumpBindPrefix: root.pumpBindPreffix
-//////// modified to control compact differently
- compact: root.compact
- Connections {
- target: scrollTimer
- onTriggered: doScroll()
- }
- }
- Tile {
- title: qsTr("TANKS")
- anchors.fill: parent
- values: TileText {
- text: qsTr("")
- width: parent.width
- wrapMode: Text.WordWrap
- }
- z: -1
- }
- }
-
-//////// added temperature ListView and Model
- ListView {
- id: tempsColumn
-
- anchors {
- top: tanksColum.bottom
- right: root.right
- }
- height: root.tempsHeight
- width: root.tankWidth
-//////// make list flickable if more tiles than will fit completely
- interactive: root.tankTileHeight * count > (tempsColumn.height + 1) ? true : false
-
- model: tempsModel
- delegate: TileTemp
- {
- width: tempsColumn.width
- height: root.tankTileHeight
-//////// modified to control compact differently
- compact: root.compact
- Connections
- {
- target: scrollTimer
- onTriggered: doScroll()
- }
- }
- Tile
- {
- title: qsTr("TEMPS")
- anchors.fill: parent
- values: TileText
- {
- text: qsTr("")
- width: parent.width
- wrapMode: Text.WordWrap
- }
- z: -1
- }
- }
- ListModel { id: tempsModel }
-
- Tile {
- id: pumpButton
-
- anchors.right: parent.right
- anchors.bottom: parent.bottom
-
- property variant texts: [ qsTr("AUTO"), qsTr("ON"), qsTr("OFF")]
- property int value: 0
- property bool reset: false
- property bool pumpEnabled: pumpRelay.value === 3
- isCurrentItem: false // not used by GuiMods key handler - focus shown a different way
- //focus: root.active && isCurrentItem // don't switch focus -- messes up key handler
-
- title: qsTr("PUMP")
- width: pumpEnabled ? root.tankWidth : 0
- height: 45
- editable: true
- readOnly: false
- color: pumpButtonMouseArea.containsPressed ? "#d3d3d3" : "#A8A8A8"
-
- VBusItem { id: pump; bind: Utils.path(settingsBindPreffix, "/Settings/Pump0/Mode") }
- VBusItem { id: pumpRelay; bind: Utils.path(settingsBindPreffix, "/Settings/Relay/Function") }
-
- values: [
- TileText {
- text: pumpButton.pumpEnabled ? qsTr("%1").arg(pumpButton.texts[pumpButton.value]) : qsTr("DISABLED")
- }
- ]
-
- function edit() {
- if (!pumpEnabled) {
- toast.createToast(qsTr("Pump functionality is not enabled. To enable it go to the relay settings page and set function to \"Tank pump\""), 5000)
- return
- }
-
- reset = true
- applyAnimation.restart()
- reset = false
-
- if (value < 2)
- value++
- else
- value = 0
- }
-
- MouseArea {
- id: pumpButtonMouseArea
- property bool containsPressed: containsMouse && pressed
- anchors.fill: parent
- onClicked: {
- parent.edit()
- }
- }
-
- Rectangle {
- id: timerRect
- height: 2
- width: pumpButton.width * 0.8
- visible: applyAnimation.running
- anchors {
- bottom: parent.bottom; bottomMargin: 5
- horizontalCenter: parent.horizontalCenter
- }
- }
-
- SequentialAnimation {
- id: applyAnimation
- alwaysRunToEnd: false
- NumberAnimation {
- target: timerRect
- property: "width"
- from: 0
- to: pumpButton.width * 0.8
- duration: 3000
- }
-
- ColorAnimation {
- target: pumpButton
- property: "color"
- from: "#A8A8A8"
- to: "#4789d0"
- duration: 200
- }
-
- ColorAnimation {
- target: pumpButton
- property: "color"
- from: "#4789d0"
- to: "#A8A8A8"
- duration: 200
- }
- PropertyAction {
- target: timerRect
- property: "width"
- value: 0
- }
- // Do not set value if the animation is restarted by user pressing the button
- // to move between options
- onCompleted: if (!pumpButton.reset) pump.setValue(pumpButton.value)
- }
- DetailTarget { id: pumpButtonTarget; detailsPage: "" }
- }
-
- // When new service is found add resources as appropriate
- Connections {
- target: DBusServices
- onDbusServiceFound: addService(service)
- }
-
- // hack to get value(s) from within a loop inside a function when service is changing
- property string tempServiceName: ""
- property VBusItem temperatureItem: VBusItem { bind: Utils.path(tempServiceName, "/Dc/0/Temperature") }
-
-//////// rewrite to use switch in place of if statements
- function addService(service)
- {
- switch (service.type)
- {
-//////// add for temp sensors
- case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR:
- numberOfTemps++
- tempsModel.append({serviceName: service.name})
- break;;
- case DBusService.DBUS_SERVICE_MULTI:
- root.tempServiceName = service.name
- if (temperatureItem.valid)
- {
- numberOfTemps++
- tempsModel.append({serviceName: service.name})
- }
- break;;
-//////// add for VE.Direct inverters
- case DBusService.DBUS_SERVICE_INVERTER:
- if (veDirectInverterService == "")
- veDirectInverterService = service.name;
- break;;
-
-//////// add for PV CHARGER voltage and current display
- case DBusService.DBUS_SERVICE_SOLAR_CHARGER:
- numberOfPvChargers++
- if (pvChargerPrefix === "")
- pvChargerPrefix = service.name;
- break;;
- case DBusService.DBUS_SERVICE_BATTERY:
- root.tempServiceName = service.name
- if (temperatureItem.valid)
- {
- numberOfTemps++
- tempsModel.append({serviceName: service.name})
- }
- break;;
- }
- }
-
- // Check available services to find tank sesnsors
-//////// rewrite to always call addService, removing redundant service type checks
- function discoverServices()
- {
- numberOfTemps = 0
- numberOfPvChargers = 0
- veDirectInverterService = ""
- tempsModel.clear()
- for (var i = 0; i < DBusServices.count; i++)
- addService(DBusServices.at(i))
- }
-
- function notificationText()
- {
- if (activeNotifications.length === 0)
- return qsTr("")
-
- var descr = []
- for (var n = 0; n < activeNotifications.length; n++) {
- var notification = activeNotifications[n];
-
- var text = notification.serviceName + " - " + notification.description;
- if (notification.value !== "" )
- text += ": " + notification.value
-
- descr.push(text)
- }
-
- return descr.join(" | ")
- }
-
- VBusItem { id: dmc; bind: Utils.path(inverterService, "/Devices/Dmc/Version") }
- VBusItem { id: bms; bind: Utils.path(inverterService, "/Devices/Bms/Version") }
-
-
-
-// Details targets
-////// display detail targets and help message when first displayed.
- Timer {
- id: helpTimer
- running: false
- repeat: false
- interval: 5000
- triggeredOnStart: true
- }
-
- // help message shown when menu is first drawn
- Rectangle
- {
- id: helpBox
- color: "white"
- width: 150
- height: 32
- opacity: 0.7
- anchors
- {
- horizontalCenter: infoArea.horizontalCenter
- verticalCenter: infoArea.verticalCenter
- }
- visible: false
- }
- TileText
- {
- text: qsTr ( "Tap tile center for detail at any time" )
- color: "black"
- anchors.fill: helpBox
- wrapMode: Text.WordWrap
- font.pixelSize: 12
- visible: helpBox.visible
- }
-
-
- //// hard key handler
- // used to press buttons when touch isn't available
- // UP and DOWN buttons cycle through the list of touch areas
- // "space" button is used to simulate a touch on the area
- // target must be highlighted so that other uses of "space"
- // will still occur
-
- // list of all details touchable areas
- // pump button sets value locally, no details page
- // so is hanelded differently
- // it must be LAST in the list because target list index is used for special processing
- property variant targetList:
- [
- multiTarget, batteryTarget, pvChargerTarget, dcSystemTarget,
- acInputTarget, acLoadsOnOutputTarget, pumpButtonTarget // pump MUST BE LAST
- ]
-
- property int selectedTarget: 0
-
- Timer
- {
- id: targetTimer
- interval: 5000
- repeat: false
- running: false
- onTriggered: { hideAllTargets () }
- }
-
- Keys.forwardTo: [keyHandler]
- Item
- {
- id: keyHandler
- Keys.onUpPressed:
- {
- nextTarget (-1)
- event.accepted = true
- }
-
- Keys.onDownPressed:
- {
- nextTarget (+1)
- event.accepted = true
- }
- Keys.onSpacePressed:
- {
- if (targetTimer.running)
- {
- var foo // hack to make clicked() work
- if (selectedTarget == targetList.length - 1)
- pumpButton.edit ()
- else
- bar.clicked (foo)
- event.accepted = true
- }
- else
- event.accepted = false
- }
- }
- // hack to make clicked() work
- property variant bar: targetList[selectedTarget]
-
- function nextTarget (increment)
- {
- // make one pass through all possible targets to find an enabled one
- // if found, that's the new selectedTarget,
- // if not selectedTarget does not change
- var newIndex = selectedTarget
- for (var i = 0; i < targetList.length; i++)
- {
- if (( ! targetTimer.running || helpBox.visible) && targetList[newIndex].enabled)
- {
- highlightSelectedTarget ()
- return
- }
- newIndex += increment
- if (newIndex >= targetList.length)
- newIndex = 0
- else if (newIndex < 0)
- newIndex = targetList.length - 1
- var includeTarget
- if (newIndex == targetList.length - 1)
- includeTarget = pumpButton.pumpEnabled
- else
- includeTarget = targetList[newIndex].enabled
- if (includeTarget)
- {
- selectedTarget = newIndex
- highlightSelectedTarget ()
- break
- }
- }
- }
-
- function showHelp ()
- {
- for (var i = 0; i < targetList.length; i++)
- {
- targetList[i].targetVisible = true
- }
- helpBox.visible = true
- targetTimer.restart ()
- }
- function hideAllTargets ()
- {
- for (var i = 0; i < targetList.length; i++)
- targetList[i].targetVisible = false
- helpBox.visible = false
- }
- function highlightSelectedTarget ()
- {
- for (var i = 0; i < targetList.length; i++)
- {
- if (targetList[i] == targetList[selectedTarget])
- targetList[i].targetVisible = true
- else
- targetList[i].targetVisible = false
- }
- targetTimer.restart ()
- helpBox.visible = false
- }
-}
diff --git a/FileSets/v3.50~5/OverviewTanksTempsDigInputs.qml b/FileSets/v3.50~5/OverviewTanksTempsDigInputs.qml
deleted file mode 100644
index 781e163c..00000000
--- a/FileSets/v3.50~5/OverviewTanksTempsDigInputs.qml
+++ /dev/null
@@ -1,207 +0,0 @@
-//// New overview page for tanks, temps and digital inputs
-//// part of GuiMods
-//// based on tank/temps column in mobile overview
-
-import QtQuick 1.1
-import com.victron.velib 1.0
-import "utils.js" as Utils
-import "timeToGo.js" as TTG
-
-OverviewPage {
- title: qsTr("Tanks & Temps & Digital Inputs")
- id: root
-
- property variant sys: theSystem
- property string systemPrefix: "com.victronenergy.system"
- property string settingsBindPreffix: "com.victronenergy.settings"
- property string pumpBindPreffix: "com.victronenergy.pump.startstop0"
-
- property int numberOfTanks: tankModel.rowCount
- property real tanksHeight: root.height
- property real minTankHeight: 21 // use for temps also
- property real maxTankHeight: 80 // use for temps also
- property real tankTileHeight: Math.min (Math.max (tanksHeight / numberOfTanks, minTankHeight), maxTankHeight)
- property bool tanksCompact: numberOfTanks > 6
-
- property int numberOfTemps: 0
- property real tempsHeight: root.height
- property real tempsTileHeight: Math.min (Math.max (tempsHeight / numberOfTemps, minTankHeight), maxTankHeight)
- property bool tempsCompact: numberOfTemps > 6
-
- property int tankWidth: parent.width / 3
- property int tempsWidth: tankWidth
- property int digInWidth: tankWidth
-
- property int numberOfDigIn: 0
- property real digInHeight: root.height
- property real digInTileHeight: Math.min (Math.max (digInHeight / numberOfDigIn, minTankHeight), maxTankHeight)
-
- Component.onCompleted: { discoverServices() }
-
- // Synchronise name text scroll start
- Timer {
- id: scrollTimer
- interval: 15000
- repeat: true
- running: root.active
- }
-
- ListView {
- id: tanksColum
-
- anchors {
- top: root.top
- left: root.left
- }
- height: root.tanksHeight
- width: root.tankWidth
- interactive: root.tankTileHeight * count > (tanksColum.height + 1) ? true : false
-
- model: TankModel { id: tankModel }
- delegate: TileTankEnhanced {
- // Without an intermediate assignment this will trigger a binding loop warning.
- property variant theService: DBusServices.get(buddy.id)
- service: theService
- width: tanksColum.width
- height: root.tankTileHeight
- pumpBindPrefix: root.pumpBindPreffix
- compact: root.tanksCompact
- Connections {
- target: scrollTimer
- onTriggered: doScroll()
- }
- }
- Tile {
- title: numberOfTanks == 0 ? qsTr ("no tanks") : qsTr("Tanks")
- anchors.fill: parent
- color: "#b3b3b3"
- values: TileText {
- text: qsTr("")
- width: parent.width
- wrapMode: Text.WordWrap
- }
- z: -1
- }
- }
-
- ListView {
- id: tempsColumn
-
- anchors {
- top: root.top
- left: tanksColum.right
- }
- height: root.tempsHeight
- width: root.tempsWidth
- // make list flickable if more tiles than will fit completely
- interactive: root.tankTileHeight * count > (tempsColumn.height + 1) ? true : false
-
- model: tempsModel
- delegate: TileTemp
- {
- width: tempsColumn.width
- height: root.tempsTileHeight
- compact: root.tempsCompact
- Connections
- {
- target: scrollTimer
- onTriggered: doScroll()
- }
- }
- Tile
- {
- title: numberOfTemps == 0 ? qsTr ("no temps") : qsTr("Temps")
- anchors.fill: parent
- color: "#b3b3b3"
- values: TileText
- {
- text: qsTr("")
- width: parent.width
- wrapMode: Text.WordWrap
- }
- z: -1
- }
- }
- ListModel { id: tempsModel }
-
- ListView {
- id: digInputsColumn
-
- anchors {
- top: root.top
- right: root.right
- }
- height: root.digInHeight
- width: root.digInWidth
- // make list flickable if more tiles than will fit completely
- interactive: root.digInTileHeight * count > (digInputsColumn.height + 1) ? true : false
-
- model: digInModel
- delegate: TileDigIn
- {
- width: digInputsColumn.width
- height: root.digInTileHeight
- }
- Tile
- {
- title: numberOfDigIn == 0 ? qsTr ("no digital inputs") : qsTr("Digital Inputs")
- anchors.fill: parent
- color: "#b3b3b3"
- values: TileText
- {
- text: qsTr("")
- width: parent.width
- wrapMode: Text.WordWrap
- }
- z: -1
- }
- }
- ListModel { id: digInModel }
-
-
- // When new service is found add resources as appropriate
- Connections {
- target: DBusServices
- onDbusServiceFound: addService(service)
- }
-
- // hack to get value(s) from within a loop inside a function when service is changing
- property string tempServiceName: ""
- property VBusItem temperatureItem: VBusItem { bind: Utils.path(tempServiceName, "/Dc/0/Temperature") }
-
- function addService(service)
- {
- switch (service.type)
- {
- case DBusService.DBUS_SERVICE_TEMPERATURE_SENSOR:
- numberOfTemps++
- tempsModel.append({serviceName: service.name})
- break;;
- case DBusService.DBUS_SERVICE_DIGITAL_INPUT:
- case DBusService.DBUS_SERVICE_PULSE_COUNTER:
- numberOfDigIn++
- digInModel.append({serviceName: service.name})
- break;;
- case DBusService.DBUS_SERVICE_BATTERY:
- case DBusService.DBUS_SERVICE_MULTI:
- root.tempServiceName = service.name
- if (temperatureItem.valid)
- {
- numberOfTemps++
- tempsModel.append({serviceName: service.name})
- }
- break;;
- }
- }
-
- // Check available services to find tank sesnsors
- function discoverServices()
- {
- numberOfTemps = 0
- tempsModel.clear()
- numberOfDigIn = 0
- digInModel.clear()
- for (var i = 0; i < DBusServices.count; i++)
- addService(DBusServices.at(i))
- }
-}
diff --git a/FileSets/v3.50~5/PageGenerator.qml b/FileSets/v3.50~5/PageGenerator.qml
deleted file mode 120000
index 0b15024e..00000000
--- a/FileSets/v3.50~5/PageGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/PageSettingsGenerator.qml b/FileSets/v3.50~5/PageSettingsGenerator.qml
deleted file mode 120000
index 5e12924e..00000000
--- a/FileSets/v3.50~5/PageSettingsGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/PageSettingsGuiMods.qml b/FileSets/v3.50~5/PageSettingsGuiMods.qml
deleted file mode 120000
index 262f3cec..00000000
--- a/FileSets/v3.50~5/PageSettingsGuiMods.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGuiMods.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/PageSettingsRelay.qml b/FileSets/v3.50~5/PageSettingsRelay.qml
deleted file mode 120000
index 41872500..00000000
--- a/FileSets/v3.50~5/PageSettingsRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/PowerGauge.qml b/FileSets/v3.50~5/PowerGauge.qml
deleted file mode 120000
index 452ca646..00000000
--- a/FileSets/v3.50~5/PowerGauge.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/TileDigIn.qml b/FileSets/v3.50~5/TileDigIn.qml
deleted file mode 120000
index f11f1206..00000000
--- a/FileSets/v3.50~5/TileDigIn.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/TileRelay.qml b/FileSets/v3.50~5/TileRelay.qml
deleted file mode 120000
index 5f86edbf..00000000
--- a/FileSets/v3.50~5/TileRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~5/dbus_digitalinputs.py b/FileSets/v3.50~5/dbus_digitalinputs.py
deleted file mode 120000
index 717bc909..00000000
--- a/FileSets/v3.50~5/dbus_digitalinputs.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.50~5/startstop.py b/FileSets/v3.50~5/startstop.py
deleted file mode 120000
index 42f29aec..00000000
--- a/FileSets/v3.50~5/startstop.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.50~7/COMPLETE b/FileSets/v3.50~7/COMPLETE
deleted file mode 100644
index e69de29b..00000000
diff --git a/FileSets/v3.50~7/DetailAcInput.qml b/FileSets/v3.50~7/DetailAcInput.qml
deleted file mode 120000
index cadafa4e..00000000
--- a/FileSets/v3.50~7/DetailAcInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailAcInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/DetailInverter.qml b/FileSets/v3.50~7/DetailInverter.qml
deleted file mode 120000
index 8a637d73..00000000
--- a/FileSets/v3.50~7/DetailInverter.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailInverter.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/DetailLoadsCombined.qml b/FileSets/v3.50~7/DetailLoadsCombined.qml
deleted file mode 120000
index 5775b2f4..00000000
--- a/FileSets/v3.50~7/DetailLoadsCombined.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsCombined.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/DetailLoadsOnInput.qml b/FileSets/v3.50~7/DetailLoadsOnInput.qml
deleted file mode 120000
index 8486816b..00000000
--- a/FileSets/v3.50~7/DetailLoadsOnInput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnInput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/DetailLoadsOnOutput.qml b/FileSets/v3.50~7/DetailLoadsOnOutput.qml
deleted file mode 120000
index 5aba1991..00000000
--- a/FileSets/v3.50~7/DetailLoadsOnOutput.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/DetailLoadsOnOutput.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/HubData.qml b/FileSets/v3.50~7/HubData.qml
deleted file mode 120000
index c7e782ee..00000000
--- a/FileSets/v3.50~7/HubData.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/HubData.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/LINKS_ONLY b/FileSets/v3.50~7/LINKS_ONLY
deleted file mode 100644
index e69de29b..00000000
diff --git a/FileSets/v3.50~7/ObjectAcConnection.qml b/FileSets/v3.50~7/ObjectAcConnection.qml
deleted file mode 120000
index 2ca36f5e..00000000
--- a/FileSets/v3.50~7/ObjectAcConnection.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/ObjectAcConnection.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewAcValuesEnhanced.qml b/FileSets/v3.50~7/OverviewAcValuesEnhanced.qml
deleted file mode 120000
index 2fcae9eb..00000000
--- a/FileSets/v3.50~7/OverviewAcValuesEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewAcValuesEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewFlowComplex.qml b/FileSets/v3.50~7/OverviewFlowComplex.qml
deleted file mode 120000
index b8f9a5f4..00000000
--- a/FileSets/v3.50~7/OverviewFlowComplex.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewFlowComplex.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewGeneratorEnhanced.qml b/FileSets/v3.50~7/OverviewGeneratorEnhanced.qml
deleted file mode 120000
index 51b929eb..00000000
--- a/FileSets/v3.50~7/OverviewGeneratorEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/OverviewGeneratorEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.50~7/OverviewGeneratorRelayEnhanced.qml
deleted file mode 120000
index 65979936..00000000
--- a/FileSets/v3.50~7/OverviewGeneratorRelayEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGeneratorRelayEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewGridParallel.qml b/FileSets/v3.50~7/OverviewGridParallel.qml
deleted file mode 120000
index 7af3834a..00000000
--- a/FileSets/v3.50~7/OverviewGridParallel.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewGridParallel.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewHub.qml b/FileSets/v3.50~7/OverviewHub.qml
deleted file mode 120000
index 88267085..00000000
--- a/FileSets/v3.50~7/OverviewHub.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHub.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewHubEnhanced.qml b/FileSets/v3.50~7/OverviewHubEnhanced.qml
deleted file mode 120000
index 2e238e43..00000000
--- a/FileSets/v3.50~7/OverviewHubEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewHubEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewMobileEnhanced.qml b/FileSets/v3.50~7/OverviewMobileEnhanced.qml
deleted file mode 120000
index a753b0fb..00000000
--- a/FileSets/v3.50~7/OverviewMobileEnhanced.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewMobileEnhanced.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/OverviewTanksTempsDigInputs.qml b/FileSets/v3.50~7/OverviewTanksTempsDigInputs.qml
deleted file mode 120000
index 5a4aaaeb..00000000
--- a/FileSets/v3.50~7/OverviewTanksTempsDigInputs.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/OverviewTanksTempsDigInputs.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/PageGenerator.qml b/FileSets/v3.50~7/PageGenerator.qml
deleted file mode 120000
index 0b15024e..00000000
--- a/FileSets/v3.50~7/PageGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/PageSettingsGenerator.qml b/FileSets/v3.50~7/PageSettingsGenerator.qml
deleted file mode 120000
index 5e12924e..00000000
--- a/FileSets/v3.50~7/PageSettingsGenerator.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGenerator.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/PageSettingsGuiMods.qml b/FileSets/v3.50~7/PageSettingsGuiMods.qml
deleted file mode 120000
index 262f3cec..00000000
--- a/FileSets/v3.50~7/PageSettingsGuiMods.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsGuiMods.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/PageSettingsRelay.qml b/FileSets/v3.50~7/PageSettingsRelay.qml
deleted file mode 120000
index 41872500..00000000
--- a/FileSets/v3.50~7/PageSettingsRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/PageSettingsRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/PowerGauge.qml b/FileSets/v3.50~7/PowerGauge.qml
deleted file mode 120000
index 452ca646..00000000
--- a/FileSets/v3.50~7/PowerGauge.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/PowerGauge.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/TileDigIn.qml b/FileSets/v3.50~7/TileDigIn.qml
deleted file mode 120000
index f11f1206..00000000
--- a/FileSets/v3.50~7/TileDigIn.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/TileDigIn.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/TileRelay.qml b/FileSets/v3.50~7/TileRelay.qml
deleted file mode 120000
index 5f86edbf..00000000
--- a/FileSets/v3.50~7/TileRelay.qml
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~25/TileRelay.qml
\ No newline at end of file
diff --git a/FileSets/v3.50~7/dbus_digitalinputs.py b/FileSets/v3.50~7/dbus_digitalinputs.py
deleted file mode 120000
index 717bc909..00000000
--- a/FileSets/v3.50~7/dbus_digitalinputs.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/dbus_digitalinputs.py
\ No newline at end of file
diff --git a/FileSets/v3.50~7/startstop.py b/FileSets/v3.50~7/startstop.py
deleted file mode 120000
index 42f29aec..00000000
--- a/FileSets/v3.50~7/startstop.py
+++ /dev/null
@@ -1 +0,0 @@
-../v3.50~22/startstop.py
\ No newline at end of file
diff --git a/FileSets/v3.50~22/COMPLETE b/FileSets/v3.51~2/COMPLETE
similarity index 100%
rename from FileSets/v3.50~22/COMPLETE
rename to FileSets/v3.51~2/COMPLETE
diff --git a/FileSets/v3.50~25/DetailAcInput.qml b/FileSets/v3.51~2/DetailAcInput.qml
similarity index 100%
rename from FileSets/v3.50~25/DetailAcInput.qml
rename to FileSets/v3.51~2/DetailAcInput.qml
diff --git a/FileSets/v3.50~25/DetailAcInput.qml.orig b/FileSets/v3.51~2/DetailAcInput.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/DetailAcInput.qml.orig
rename to FileSets/v3.51~2/DetailAcInput.qml.orig
diff --git a/FileSets/v3.50~25/DetailInverter.qml b/FileSets/v3.51~2/DetailInverter.qml
similarity index 100%
rename from FileSets/v3.50~25/DetailInverter.qml
rename to FileSets/v3.51~2/DetailInverter.qml
diff --git a/FileSets/v3.50~25/DetailInverter.qml.orig b/FileSets/v3.51~2/DetailInverter.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/DetailInverter.qml.orig
rename to FileSets/v3.51~2/DetailInverter.qml.orig
diff --git a/FileSets/v3.50~25/DetailLoadsCombined.qml b/FileSets/v3.51~2/DetailLoadsCombined.qml
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsCombined.qml
rename to FileSets/v3.51~2/DetailLoadsCombined.qml
diff --git a/FileSets/v3.50~25/DetailLoadsCombined.qml.orig b/FileSets/v3.51~2/DetailLoadsCombined.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsCombined.qml.orig
rename to FileSets/v3.51~2/DetailLoadsCombined.qml.orig
diff --git a/FileSets/v3.50~25/DetailLoadsOnInput.qml b/FileSets/v3.51~2/DetailLoadsOnInput.qml
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsOnInput.qml
rename to FileSets/v3.51~2/DetailLoadsOnInput.qml
diff --git a/FileSets/v3.50~25/DetailLoadsOnInput.qml.orig b/FileSets/v3.51~2/DetailLoadsOnInput.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsOnInput.qml.orig
rename to FileSets/v3.51~2/DetailLoadsOnInput.qml.orig
diff --git a/FileSets/v3.50~25/DetailLoadsOnOutput.qml b/FileSets/v3.51~2/DetailLoadsOnOutput.qml
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsOnOutput.qml
rename to FileSets/v3.51~2/DetailLoadsOnOutput.qml
diff --git a/FileSets/v3.50~25/DetailLoadsOnOutput.qml.orig b/FileSets/v3.51~2/DetailLoadsOnOutput.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/DetailLoadsOnOutput.qml.orig
rename to FileSets/v3.51~2/DetailLoadsOnOutput.qml.orig
diff --git a/FileSets/v3.50~25/HubData.qml b/FileSets/v3.51~2/HubData.qml
similarity index 100%
rename from FileSets/v3.50~25/HubData.qml
rename to FileSets/v3.51~2/HubData.qml
diff --git a/FileSets/v3.50~25/HubData.qml.orig b/FileSets/v3.51~2/HubData.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/HubData.qml.orig
rename to FileSets/v3.51~2/HubData.qml.orig
diff --git a/FileSets/v3.50~25/ObjectAcConnection.qml b/FileSets/v3.51~2/ObjectAcConnection.qml
similarity index 100%
rename from FileSets/v3.50~25/ObjectAcConnection.qml
rename to FileSets/v3.51~2/ObjectAcConnection.qml
diff --git a/FileSets/v3.50~25/ObjectAcConnection.qml.orig b/FileSets/v3.51~2/ObjectAcConnection.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/ObjectAcConnection.qml.orig
rename to FileSets/v3.51~2/ObjectAcConnection.qml.orig
diff --git a/FileSets/v3.50~25/OverviewAcValuesEnhanced.qml b/FileSets/v3.51~2/OverviewAcValuesEnhanced.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewAcValuesEnhanced.qml
rename to FileSets/v3.51~2/OverviewAcValuesEnhanced.qml
diff --git a/FileSets/v3.50~25/OverviewAcValuesEnhanced.qml.orig b/FileSets/v3.51~2/OverviewAcValuesEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewAcValuesEnhanced.qml.orig
rename to FileSets/v3.51~2/OverviewAcValuesEnhanced.qml.orig
diff --git a/FileSets/v3.50~25/OverviewFlowComplex.qml b/FileSets/v3.51~2/OverviewFlowComplex.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewFlowComplex.qml
rename to FileSets/v3.51~2/OverviewFlowComplex.qml
diff --git a/FileSets/v3.50~25/OverviewFlowComplex.qml.orig b/FileSets/v3.51~2/OverviewFlowComplex.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewFlowComplex.qml.orig
rename to FileSets/v3.51~2/OverviewFlowComplex.qml.orig
diff --git a/FileSets/v3.50~22/OverviewGeneratorEnhanced.qml b/FileSets/v3.51~2/OverviewGeneratorEnhanced.qml
similarity index 97%
rename from FileSets/v3.50~22/OverviewGeneratorEnhanced.qml
rename to FileSets/v3.51~2/OverviewGeneratorEnhanced.qml
index 0c189315..8c8c679a 100644
--- a/FileSets/v3.50~22/OverviewGeneratorEnhanced.qml
+++ b/FileSets/v3.51~2/OverviewGeneratorEnhanced.qml
@@ -43,7 +43,6 @@ OverviewPage {
property VBusItem externalOverrideItem: VBusItem { bind: Utils.path(bindPrefix, "/ExternalOverride") }
property bool externalOverride: externalOverrideItem.valid && externalOverrideItem.value == 1 && ! errors
- property VBusItem runningState: VBusItem { bind: Utils.path(bindPrefix, "/GeneratorRunningState") }
VBusItem { id: showGaugesItem; bind: Utils.path(guiModsPrefix, "/ShowGauges") }
property bool showGauges: showGaugesItem.valid ? showGaugesItem.value === 1 ? true : false : false
@@ -240,7 +239,7 @@ OverviewPage {
},
TileText {
width: imageTile.width - 5
- text: runningState.valid ? runningState.value == "R" ? "Running " : runningState.value == "S" ? "Stopped " : "" : ""
+ text: runningTime.valid ? runningTime.value > 0 ? qsTr ("Running ") : qsTr ("Stopped ") : " "
}
]
}
@@ -269,7 +268,10 @@ OverviewPage {
if (!root.state.valid)
message = ""
else if (externalOverride)
- message = qsTr("External Override - stopped")
+ if (root.state.value === 0)
+ message = qsTr("External Override - running")
+ else
+ message = qsTr("External Override - stopped")
else if (root.state.value === 3)
message = qsTr("Cool-down")
else if (root.state.value === 4)
diff --git a/FileSets/v3.50~25/OverviewGeneratorEnhanced.qml.orig b/FileSets/v3.51~2/OverviewGeneratorEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewGeneratorEnhanced.qml.orig
rename to FileSets/v3.51~2/OverviewGeneratorEnhanced.qml.orig
diff --git a/FileSets/v3.50~25/OverviewGeneratorRelayEnhanced.qml b/FileSets/v3.51~2/OverviewGeneratorRelayEnhanced.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewGeneratorRelayEnhanced.qml
rename to FileSets/v3.51~2/OverviewGeneratorRelayEnhanced.qml
diff --git a/FileSets/v3.50~25/OverviewGeneratorRelayEnhanced.qml.orig b/FileSets/v3.51~2/OverviewGeneratorRelayEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewGeneratorRelayEnhanced.qml.orig
rename to FileSets/v3.51~2/OverviewGeneratorRelayEnhanced.qml.orig
diff --git a/FileSets/v3.50~25/OverviewGridParallel.qml b/FileSets/v3.51~2/OverviewGridParallel.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewGridParallel.qml
rename to FileSets/v3.51~2/OverviewGridParallel.qml
diff --git a/FileSets/v3.50~25/OverviewGridParallel.qml.orig b/FileSets/v3.51~2/OverviewGridParallel.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewGridParallel.qml.orig
rename to FileSets/v3.51~2/OverviewGridParallel.qml.orig
diff --git a/FileSets/v3.50~25/OverviewHub.qml b/FileSets/v3.51~2/OverviewHub.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewHub.qml
rename to FileSets/v3.51~2/OverviewHub.qml
diff --git a/FileSets/v3.50~25/OverviewHub.qml.orig b/FileSets/v3.51~2/OverviewHub.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewHub.qml.orig
rename to FileSets/v3.51~2/OverviewHub.qml.orig
diff --git a/FileSets/v3.50~25/OverviewHubEnhanced.qml b/FileSets/v3.51~2/OverviewHubEnhanced.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewHubEnhanced.qml
rename to FileSets/v3.51~2/OverviewHubEnhanced.qml
diff --git a/FileSets/v3.50~25/OverviewHubEnhanced.qml.orig b/FileSets/v3.51~2/OverviewHubEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewHubEnhanced.qml.orig
rename to FileSets/v3.51~2/OverviewHubEnhanced.qml.orig
diff --git a/FileSets/v3.50~25/OverviewMobileEnhanced.qml b/FileSets/v3.51~2/OverviewMobileEnhanced.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewMobileEnhanced.qml
rename to FileSets/v3.51~2/OverviewMobileEnhanced.qml
diff --git a/FileSets/v3.50~25/OverviewMobileEnhanced.qml.orig b/FileSets/v3.51~2/OverviewMobileEnhanced.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewMobileEnhanced.qml.orig
rename to FileSets/v3.51~2/OverviewMobileEnhanced.qml.orig
diff --git a/FileSets/v3.50~25/OverviewTanksTempsDigInputs.qml b/FileSets/v3.51~2/OverviewTanksTempsDigInputs.qml
similarity index 100%
rename from FileSets/v3.50~25/OverviewTanksTempsDigInputs.qml
rename to FileSets/v3.51~2/OverviewTanksTempsDigInputs.qml
diff --git a/FileSets/v3.50~25/OverviewTanksTempsDigInputs.qml.orig b/FileSets/v3.51~2/OverviewTanksTempsDigInputs.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/OverviewTanksTempsDigInputs.qml.orig
rename to FileSets/v3.51~2/OverviewTanksTempsDigInputs.qml.orig
diff --git a/FileSets/v3.50~25/PageGenerator.qml.USE_ORIGINAL b/FileSets/v3.51~2/PageGenerator.qml.USE_ORIGINAL
similarity index 100%
rename from FileSets/v3.50~25/PageGenerator.qml.USE_ORIGINAL
rename to FileSets/v3.51~2/PageGenerator.qml.USE_ORIGINAL
diff --git a/FileSets/v3.50~25/PageSettingsGenerator.qml b/FileSets/v3.51~2/PageSettingsGenerator.qml
similarity index 100%
rename from FileSets/v3.50~25/PageSettingsGenerator.qml
rename to FileSets/v3.51~2/PageSettingsGenerator.qml
diff --git a/FileSets/v3.50~25/PageSettingsGenerator.qml.orig b/FileSets/v3.51~2/PageSettingsGenerator.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/PageSettingsGenerator.qml.orig
rename to FileSets/v3.51~2/PageSettingsGenerator.qml.orig
diff --git a/FileSets/v3.50~22/PageSettingsGuiMods.qml b/FileSets/v3.51~2/PageSettingsGuiMods.qml
similarity index 100%
rename from FileSets/v3.50~22/PageSettingsGuiMods.qml
rename to FileSets/v3.51~2/PageSettingsGuiMods.qml
diff --git a/FileSets/v3.50~25/PageSettingsGuiMods.qml.orig b/FileSets/v3.51~2/PageSettingsGuiMods.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/PageSettingsGuiMods.qml.orig
rename to FileSets/v3.51~2/PageSettingsGuiMods.qml.orig
diff --git a/FileSets/v3.50~25/PageSettingsRelay.qml b/FileSets/v3.51~2/PageSettingsRelay.qml
similarity index 99%
rename from FileSets/v3.50~25/PageSettingsRelay.qml
rename to FileSets/v3.51~2/PageSettingsRelay.qml
index 7bb4cfaa..44c08871 100644
--- a/FileSets/v3.50~25/PageSettingsRelay.qml
+++ b/FileSets/v3.51~2/PageSettingsRelay.qml
@@ -93,7 +93,7 @@ MbPage {
bind: Utils.path(bindPrefix, "/Settings/Relay/Function")
possibleValues:[
MbOption { description: qsTr("Alarm relay"); value: 0 },
- MbOption { description: qsTr("Relay controlled genset"); value: 1 },
+ MbOption { description: qsTr("Genset start/stop"); value: 1 },
MbOption { description: qsTr("Connected genset helper relay"); value: 5 },
MbOption { description: qsTr("Tank pump"); value: 3 },
MbOption { description: qsTr("Manual"); value: 2 },
diff --git a/FileSets/v3.50~25/PageSettingsRelay.qml.orig b/FileSets/v3.51~2/PageSettingsRelay.qml.orig
similarity index 96%
rename from FileSets/v3.50~25/PageSettingsRelay.qml.orig
rename to FileSets/v3.51~2/PageSettingsRelay.qml.orig
index 7cf81e4c..ba6ea3e5 100644
--- a/FileSets/v3.50~25/PageSettingsRelay.qml.orig
+++ b/FileSets/v3.51~2/PageSettingsRelay.qml.orig
@@ -17,7 +17,7 @@ MbPage {
bind: Utils.path(bindPrefix, "/Settings/Relay/Function")
possibleValues:[
MbOption { description: qsTr("Alarm relay"); value: 0 },
- MbOption { description: qsTr("Relay controlled genset"); value: 1 },
+ MbOption { description: qsTr("Genset start/stop"); value: 1 },
MbOption { description: qsTr("Connected genset helper relay"); value: 5 },
MbOption { description: qsTr("Tank pump"); value: 3 },
MbOption { description: qsTr("Manual"); value: 2 },
diff --git a/FileSets/v3.50~25/PowerGauge.qml b/FileSets/v3.51~2/PowerGauge.qml
similarity index 100%
rename from FileSets/v3.50~25/PowerGauge.qml
rename to FileSets/v3.51~2/PowerGauge.qml
diff --git a/FileSets/v3.50~25/PowerGauge.qml.orig b/FileSets/v3.51~2/PowerGauge.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/PowerGauge.qml.orig
rename to FileSets/v3.51~2/PowerGauge.qml.orig
diff --git a/FileSets/v3.50~22/TileDigIn.qml b/FileSets/v3.51~2/TileDigIn.qml
similarity index 100%
rename from FileSets/v3.50~22/TileDigIn.qml
rename to FileSets/v3.51~2/TileDigIn.qml
diff --git a/FileSets/v3.50~25/TileDigIn.qml.orig b/FileSets/v3.51~2/TileDigIn.qml.orig
similarity index 82%
rename from FileSets/v3.50~25/TileDigIn.qml.orig
rename to FileSets/v3.51~2/TileDigIn.qml.orig
index 473def73..25ac69b0 100755
--- a/FileSets/v3.50~25/TileDigIn.qml.orig
+++ b/FileSets/v3.51~2/TileDigIn.qml.orig
@@ -16,7 +16,7 @@ from gi.repository import GLib
from vedbus import VeDbusService, VeDbusItemImport
from settingsdevice import SettingsDevice
-VERSION = '0.24'
+VERSION = '0.27'
MAXCOUNT = 2**31-1
SAVEINTERVAL = 60000
@@ -60,6 +60,15 @@ class SessionBus(dbus.bus.BusConnection):
def __new__(cls):
return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
+class InputPin():
+ devid = None
+ devinstance = None
+
+ def __init__(self, name=None, path=None, label=None):
+ self.name = name
+ self.path = path
+ self.label = label
+
class BasePulseCounter(object):
pass
@@ -95,8 +104,11 @@ class EpollPulseCounter(BasePulseCounter):
path = os.path.realpath(path)
# Set up gpio for rising edge interrupts
- with open(os.path.join(path, 'edge'), 'ab') as fp:
- fp.write(b'both')
+ try:
+ with open(os.path.join(path, 'edge'), 'ab') as fp:
+ fp.write(b'both')
+ except:
+ pass
fp = open(os.path.join(path, 'value'), 'rb')
level = int(fp.read()) # flush it in case it's high at startup
@@ -179,21 +191,25 @@ class PinHandler(object, metaclass=HandlerMaker):
_product_name = 'Generic GPIO'
dbus_name = "digital"
def __init__(self, bus, base, path, gpio, settings):
- self.gpio = gpio
- self.path = path
self.bus = bus
self.settings = settings
self._level = 0 # Remember last state
+ instance = int(settings['instance'].split(':')[1])
+
+ name = str(gpio)
+ if name[0].isdecimal():
+ name = 'input_' + name
+
self.service = VeDbusService(
- "{}.{}.input{:02d}".format(base, self.dbus_name, gpio), bus=bus,
+ "{}.{}.{}".format(base, self.dbus_name, name), bus=bus,
register=False)
# Add objects required by ve-api
self.service.add_path('/Mgmt/ProcessName', __file__)
self.service.add_path('/Mgmt/ProcessVersion', VERSION)
self.service.add_path('/Mgmt/Connection', path)
- self.service.add_path('/DeviceInstance', gpio)
+ self.service.add_path('/DeviceInstance', instance)
self.service.add_path('/ProductId', self.product_id)
self.service.add_path('/ProductName', self.product_name)
self.service.add_path('/Connected', 1)
@@ -401,9 +417,11 @@ class Generator(PinAlarm):
_product_name = "Generator"
type_id = 9
translation = 5 # running, stopped
+ startStopService = 'com.victronenergy.generator.startstop0'
- def __init__(self, *args, **kwargs):
- super(Generator, self).__init__(*args, **kwargs)
+ def __init__(self, bus, base, path, gpio, settings):
+ super(Generator, self).__init__(bus, base, path, gpio, settings)
+ self._gpio = gpio
# Periodically rewrite the generator selection. The Multi may reset
# causing this to be lost, or a race condition on startup may cause
# it to not be set properly.
@@ -416,7 +434,15 @@ class Generator(PinAlarm):
services = [n for n in self.bus.list_names() if n.startswith(
'com.victronenergy.vebus.')]
for n in services:
- self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', None,
+ self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [v], None, None)
+ except dbus.exceptions.DBusException:
+ print ("DBus exception setting RemoteGeneratorSelected")
+ traceback.print_exc()
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [self._gpio], None, None)
+ self.bus.call_async(self.startStopService, '/DigitalInput/Running', 'com.victronenergy.BusItem',
'SetValue', 'v', [v], None, None)
except dbus.exceptions.DBusException:
print ("DBus exception setting RemoteGeneratorSelected")
@@ -432,7 +458,11 @@ class Generator(PinAlarm):
super(Generator, self).deactivate()
# When deactivating, reset the generator selection state
self.select_generator(0)
-
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [0], None, None)
+ except dbus.exceptions.DBusException:
+ pass
# And kill the periodic job
GLib.source_remove(self._timer)
self._timer = None
@@ -482,6 +512,29 @@ class GenericIO(PinAlarm):
def dbusconnection():
return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
+def parse_config(conf):
+ f = open(conf)
+
+ tag = None
+ pins = []
+
+ for line in f:
+ cmd, arg = line.strip().split(maxsplit=1)
+
+ if cmd == 'tag':
+ tag = arg
+ continue
+
+ if cmd == 'input':
+ pth, label = arg.split(maxsplit=1)
+ label = label.strip('"')
+ pin = InputPin(tag + '_' + os.path.basename(pth), pth, label)
+ pins.append(pin)
+ continue
+
+ f.close()
+
+ return pins
def main():
parser = ArgumentParser(description=sys.argv[0])
@@ -491,7 +544,8 @@ def main():
parser.add_argument('--poll',
help='Use a different kind of polling. Options are epoll, dumb and debug',
default='epoll')
- parser.add_argument('inputs', nargs='+', help='Path to digital input')
+ parser.add_argument('--conf', action='append', default=[], help='Config file')
+ parser.add_argument('inputs', nargs='*', help='Path to digital input')
args = parser.parse_args()
PulseCounter = {
@@ -501,6 +555,9 @@ def main():
DBusGMainLoop(set_as_default=True)
+ ctlbus = dbusconnection()
+ ctlsvc = VeDbusService(args.servicebase + '.digitalinputs', bus=ctlbus, register=True)
+
# Keep track of enabled services
services = {}
inputs = dict(enumerate(args.inputs, 1))
@@ -521,15 +578,18 @@ def main():
def unregister_gpio(gpio):
print ("unRegistering GPIO {}".format(gpio))
- pulses.unregister(gpio)
- services[gpio].deactivate()
+ if pulses.registered(gpio):
+ pulses.unregister(gpio)
+ services[gpio].deactivate()
- def handle_setting_change(inp, setting, old, new):
+ def handle_setting_change(pin, setting, old, new):
# This handler may also be called if some attribute of a setting
# is changed, but not the value. Bail if the value is unchanged.
if old == new:
return
+ inp = pin.name
+
if setting == 'inputtype':
if new:
# Get current bus and settings objects, to be reused
@@ -540,6 +600,12 @@ def main():
if pulses.registered(inp):
unregister_gpio(inp)
+ # We only want 1 generator input at a time, so disable other inputs configured as generator.
+ for i in inputs:
+ if i != inp and services[i].settings['inputtype'] == 9 == new:
+ services[i].settings['inputtype'] = 0
+ unregister_gpio(i)
+
# Before registering the new input, reset its settings to defaults
settings['count'] = 0
settings['invert'] = 0
@@ -547,10 +613,12 @@ def main():
settings['alarm'] = 0
# Register it
- register_gpio(inputs[inp], inp, bus, settings)
+ register_gpio(pin.path, inp, bus, settings)
elif old:
# Input disabled
unregister_gpio(inp)
+
+ ctlsvc['/Devices/{}/Type'.format(inp)] = new
elif setting in ('rate', 'invert', 'alarm', 'invertalarm'):
services[inp].refresh()
elif setting == 'name':
@@ -560,11 +628,31 @@ def main():
# if it has changed.
v = int(new)
s = services[inp]
- if s.count != v:
+ if s.active and s.count != v:
s.count = v
s.refresh()
+ def change_type(sd, path, val):
+ if not 0 <= val < len(INPUTTYPES):
+ return False
+ sd['inputtype'] = val
+ return True
+
+ pins = []
+
for inp, pth in inputs.items():
+ pin = InputPin(inp, pth, 'Digital input {}'.format(inp))
+ pin.devid = os.path.basename(pth)
+ pin.devinstance = inp
+ pins.append(pin)
+
+ for conf in args.conf:
+ pins += parse_config(conf)
+
+ for pin in pins:
+ inp = pin.name
+ devid = pin.devid or pin.name
+ inst = 'digitalinput:{}'.format(pin.devinstance or 10)
supported_settings = {
'inputtype': ['/Settings/DigitalInput/{}/Type'.format(inp), 0, 0, len(INPUTTYPES)-1],
'rate': ['/Settings/DigitalInput/{}/Multiplier'.format(inp), 0.001, 0, 1.0],
@@ -573,10 +661,14 @@ def main():
'invertalarm': ['/Settings/DigitalInput/{}/InvertAlarm'.format(inp), 0, 0, 1],
'alarm': ['/Settings/DigitalInput/{}/AlarmSetting'.format(inp), 0, 0, 1],
'name': ['/Settings/DigitalInput/{}/CustomName'.format(inp), '', '', ''],
+ 'instance': ['/Settings/Devices/{}/ClassAndVrmInstance'.format(devid), inst, '', ''],
}
bus = dbusconnection()
- sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, inp), timeout=10)
- register_gpio(pth, inp, bus, sd)
+ sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, pin), timeout=10)
+ register_gpio(pin.path, inp, bus, sd)
+ ctlsvc.add_path('/Devices/{}/Label'.format(inp), pin.label)
+ ctlsvc.add_path('/Devices/{}/Type'.format(inp), sd['inputtype'],
+ writeable=True, onchangecallback=partial(change_type, sd))
def poll(mainloop):
from time import time
@@ -604,8 +696,8 @@ def main():
# Periodically save the counter
def save_counters():
- for inp in inputs:
- services[inp].save_count()
+ for svc in services.values():
+ svc.save_count()
return True
GLib.timeout_add(SAVEINTERVAL, save_counters)
diff --git a/FileSets/v3.50~25/TileRelay.qml b/FileSets/v3.51~2/TileRelay.qml
similarity index 100%
rename from FileSets/v3.50~25/TileRelay.qml
rename to FileSets/v3.51~2/TileRelay.qml
diff --git a/FileSets/v3.50~25/TileRelay.qml.orig b/FileSets/v3.51~2/TileRelay.qml.orig
similarity index 100%
rename from FileSets/v3.50~25/TileRelay.qml.orig
rename to FileSets/v3.51~2/TileRelay.qml.orig
diff --git a/FileSets/v3.50~25/dbus_digitalinputs.py b/FileSets/v3.51~2/dbus_digitalinputs.py
old mode 100755
new mode 100644
similarity index 82%
rename from FileSets/v3.50~25/dbus_digitalinputs.py
rename to FileSets/v3.51~2/dbus_digitalinputs.py
index 938b8ee1..b8864152
--- a/FileSets/v3.50~25/dbus_digitalinputs.py
+++ b/FileSets/v3.51~2/dbus_digitalinputs.py
@@ -18,7 +18,7 @@
from vedbus import VeDbusService, VeDbusItemImport
from settingsdevice import SettingsDevice
-VERSION = '0.24'
+VERSION = '0.27'
MAXCOUNT = 2**31-1
SAVEINTERVAL = 60000
@@ -66,6 +66,15 @@ class SessionBus(dbus.bus.BusConnection):
def __new__(cls):
return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
+class InputPin():
+ devid = None
+ devinstance = None
+
+ def __init__(self, name=None, path=None, label=None):
+ self.name = name
+ self.path = path
+ self.label = label
+
class BasePulseCounter(object):
pass
@@ -101,8 +110,11 @@ def register(self, path, gpio):
path = os.path.realpath(path)
# Set up gpio for rising edge interrupts
- with open(os.path.join(path, 'edge'), 'ab') as fp:
- fp.write(b'both')
+ try:
+ with open(os.path.join(path, 'edge'), 'ab') as fp:
+ fp.write(b'both')
+ except:
+ pass
fp = open(os.path.join(path, 'value'), 'rb')
level = int(fp.read()) # flush it in case it's high at startup
@@ -185,21 +197,25 @@ class PinHandler(object, metaclass=HandlerMaker):
_product_name = 'Generic GPIO'
dbus_name = "digital"
def __init__(self, bus, base, path, gpio, settings):
- self.gpio = gpio
- self.path = path
self.bus = bus
self.settings = settings
self._level = 0 # Remember last state
+ instance = int(settings['instance'].split(':')[1])
+
+ name = str(gpio)
+ if name[0].isdecimal():
+ name = 'input_' + name
+
self.service = VeDbusService(
- "{}.{}.input{:02d}".format(base, self.dbus_name, gpio), bus=bus,
+ "{}.{}.{}".format(base, self.dbus_name, name), bus=bus,
register=False)
# Add objects required by ve-api
self.service.add_path('/Mgmt/ProcessName', __file__)
self.service.add_path('/Mgmt/ProcessVersion', VERSION)
self.service.add_path('/Mgmt/Connection', path)
- self.service.add_path('/DeviceInstance', gpio)
+ self.service.add_path('/DeviceInstance', instance)
self.service.add_path('/ProductId', self.product_id)
self.service.add_path('/ProductName', self.product_name)
self.service.add_path('/Connected', 1)
@@ -407,9 +423,11 @@ class Generator(PinAlarm):
_product_name = "Generator"
type_id = 9
translation = 5 # running, stopped
+ startStopService = 'com.victronenergy.generator.startstop0'
- def __init__(self, *args, **kwargs):
- super(Generator, self).__init__(*args, **kwargs)
+ def __init__(self, bus, base, path, gpio, settings):
+ super(Generator, self).__init__(bus, base, path, gpio, settings)
+ self._gpio = gpio
# Periodically rewrite the generator selection. The Multi may reset
# causing this to be lost, or a race condition on startup may cause
# it to not be set properly.
@@ -418,11 +436,9 @@ def __init__(self, *args, **kwargs):
#### added for ExtTransferSwitch package
self.mainVeBusServiceItem = None
-#### end added for ExtTransferSwitch package
def select_generator(self, v):
-
# Find all vebus services, and let them know
try:
services = [n for n in self.bus.list_names() if n.startswith(
@@ -441,7 +457,15 @@ def select_generator(self, v):
pass
#### end added for ExtTransferSwitch package
- self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', None,
+ self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [v], None, None)
+ except dbus.exceptions.DBusException:
+ print ("DBus exception setting RemoteGeneratorSelected")
+ traceback.print_exc()
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [self._gpio], None, None)
+ self.bus.call_async(self.startStopService, '/DigitalInput/Running', 'com.victronenergy.BusItem',
'SetValue', 'v', [v], None, None)
except dbus.exceptions.DBusException:
print ("DBus exception setting RemoteGeneratorSelected")
@@ -457,11 +481,14 @@ def deactivate(self):
super(Generator, self).deactivate()
# When deactivating, reset the generator selection state
self.select_generator(0)
-
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [0], None, None)
+ except dbus.exceptions.DBusException:
+ pass
# And kill the periodic job
- if self._timer is not None:
- GLib.source_remove(self._timer)
- self._timer = None
+ GLib.source_remove(self._timer)
+ self._timer = None
# Various types of things we might want to monitor
class DoorSensor(PinAlarm):
@@ -514,6 +541,29 @@ class TransferSwitch(PinAlarm):
def dbusconnection():
return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
+def parse_config(conf):
+ f = open(conf)
+
+ tag = None
+ pins = []
+
+ for line in f:
+ cmd, arg = line.strip().split(maxsplit=1)
+
+ if cmd == 'tag':
+ tag = arg
+ continue
+
+ if cmd == 'input':
+ pth, label = arg.split(maxsplit=1)
+ label = label.strip('"')
+ pin = InputPin(tag + '_' + os.path.basename(pth), pth, label)
+ pins.append(pin)
+ continue
+
+ f.close()
+
+ return pins
def main():
parser = ArgumentParser(description=sys.argv[0])
@@ -523,7 +573,8 @@ def main():
parser.add_argument('--poll',
help='Use a different kind of polling. Options are epoll, dumb and debug',
default='epoll')
- parser.add_argument('inputs', nargs='+', help='Path to digital input')
+ parser.add_argument('--conf', action='append', default=[], help='Config file')
+ parser.add_argument('inputs', nargs='*', help='Path to digital input')
args = parser.parse_args()
PulseCounter = {
@@ -533,6 +584,9 @@ def main():
DBusGMainLoop(set_as_default=True)
+ ctlbus = dbusconnection()
+ ctlsvc = VeDbusService(args.servicebase + '.digitalinputs', bus=ctlbus, register=True)
+
# Keep track of enabled services
services = {}
inputs = dict(enumerate(args.inputs, 1))
@@ -553,15 +607,18 @@ def register_gpio(path, gpio, bus, settings):
def unregister_gpio(gpio):
print ("unRegistering GPIO {}".format(gpio))
- pulses.unregister(gpio)
- services[gpio].deactivate()
+ if pulses.registered(gpio):
+ pulses.unregister(gpio)
+ services[gpio].deactivate()
- def handle_setting_change(inp, setting, old, new):
+ def handle_setting_change(pin, setting, old, new):
# This handler may also be called if some attribute of a setting
# is changed, but not the value. Bail if the value is unchanged.
if old == new:
return
+ inp = pin.name
+
if setting == 'inputtype':
if new:
# Get current bus and settings objects, to be reused
@@ -572,6 +629,12 @@ def handle_setting_change(inp, setting, old, new):
if pulses.registered(inp):
unregister_gpio(inp)
+ # We only want 1 generator input at a time, so disable other inputs configured as generator.
+ for i in inputs:
+ if i != inp and services[i].settings['inputtype'] == 9 == new:
+ services[i].settings['inputtype'] = 0
+ unregister_gpio(i)
+
# Before registering the new input, reset its settings to defaults
settings['count'] = 0
settings['invert'] = 0
@@ -579,10 +642,12 @@ def handle_setting_change(inp, setting, old, new):
settings['alarm'] = 0
# Register it
- register_gpio(inputs[inp], inp, bus, settings)
+ register_gpio(pin.path, inp, bus, settings)
elif old:
# Input disabled
unregister_gpio(inp)
+
+ ctlsvc['/Devices/{}/Type'.format(inp)] = new
elif setting in ('rate', 'invert', 'alarm', 'invertalarm'):
services[inp].refresh()
elif setting == 'name':
@@ -592,11 +657,31 @@ def handle_setting_change(inp, setting, old, new):
# if it has changed.
v = int(new)
s = services[inp]
- if s.count != v:
+ if s.active and s.count != v:
s.count = v
s.refresh()
+ def change_type(sd, path, val):
+ if not 0 <= val < len(INPUTTYPES):
+ return False
+ sd['inputtype'] = val
+ return True
+
+ pins = []
+
for inp, pth in inputs.items():
+ pin = InputPin(inp, pth, 'Digital input {}'.format(inp))
+ pin.devid = os.path.basename(pth)
+ pin.devinstance = inp
+ pins.append(pin)
+
+ for conf in args.conf:
+ pins += parse_config(conf)
+
+ for pin in pins:
+ inp = pin.name
+ devid = pin.devid or pin.name
+ inst = 'digitalinput:{}'.format(pin.devinstance or 10)
supported_settings = {
'inputtype': ['/Settings/DigitalInput/{}/Type'.format(inp), 0, 0, len(INPUTTYPES)-1],
'rate': ['/Settings/DigitalInput/{}/Multiplier'.format(inp), 0.001, 0, 1.0],
@@ -605,10 +690,14 @@ def handle_setting_change(inp, setting, old, new):
'invertalarm': ['/Settings/DigitalInput/{}/InvertAlarm'.format(inp), 0, 0, 1],
'alarm': ['/Settings/DigitalInput/{}/AlarmSetting'.format(inp), 0, 0, 1],
'name': ['/Settings/DigitalInput/{}/CustomName'.format(inp), '', '', ''],
+ 'instance': ['/Settings/Devices/{}/ClassAndVrmInstance'.format(devid), inst, '', ''],
}
bus = dbusconnection()
- sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, inp), timeout=10)
- register_gpio(pth, inp, bus, sd)
+ sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, pin), timeout=10)
+ register_gpio(pin.path, inp, bus, sd)
+ ctlsvc.add_path('/Devices/{}/Label'.format(inp), pin.label)
+ ctlsvc.add_path('/Devices/{}/Type'.format(inp), sd['inputtype'],
+ writeable=True, onchangecallback=partial(change_type, sd))
def poll(mainloop):
from time import time
@@ -636,8 +725,8 @@ def poll(mainloop):
# Periodically save the counter
def save_counters():
- for inp in inputs:
- services[inp].save_count()
+ for svc in services.values():
+ svc.save_count()
return True
GLib.timeout_add(SAVEINTERVAL, save_counters)
diff --git a/FileSets/v3.50~25/dbus_digitalinputs.py.orig b/FileSets/v3.51~2/dbus_digitalinputs.py.orig
similarity index 82%
rename from FileSets/v3.50~25/dbus_digitalinputs.py.orig
rename to FileSets/v3.51~2/dbus_digitalinputs.py.orig
index 473def73..25ac69b0 100755
--- a/FileSets/v3.50~25/dbus_digitalinputs.py.orig
+++ b/FileSets/v3.51~2/dbus_digitalinputs.py.orig
@@ -16,7 +16,7 @@ from gi.repository import GLib
from vedbus import VeDbusService, VeDbusItemImport
from settingsdevice import SettingsDevice
-VERSION = '0.24'
+VERSION = '0.27'
MAXCOUNT = 2**31-1
SAVEINTERVAL = 60000
@@ -60,6 +60,15 @@ class SessionBus(dbus.bus.BusConnection):
def __new__(cls):
return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
+class InputPin():
+ devid = None
+ devinstance = None
+
+ def __init__(self, name=None, path=None, label=None):
+ self.name = name
+ self.path = path
+ self.label = label
+
class BasePulseCounter(object):
pass
@@ -95,8 +104,11 @@ class EpollPulseCounter(BasePulseCounter):
path = os.path.realpath(path)
# Set up gpio for rising edge interrupts
- with open(os.path.join(path, 'edge'), 'ab') as fp:
- fp.write(b'both')
+ try:
+ with open(os.path.join(path, 'edge'), 'ab') as fp:
+ fp.write(b'both')
+ except:
+ pass
fp = open(os.path.join(path, 'value'), 'rb')
level = int(fp.read()) # flush it in case it's high at startup
@@ -179,21 +191,25 @@ class PinHandler(object, metaclass=HandlerMaker):
_product_name = 'Generic GPIO'
dbus_name = "digital"
def __init__(self, bus, base, path, gpio, settings):
- self.gpio = gpio
- self.path = path
self.bus = bus
self.settings = settings
self._level = 0 # Remember last state
+ instance = int(settings['instance'].split(':')[1])
+
+ name = str(gpio)
+ if name[0].isdecimal():
+ name = 'input_' + name
+
self.service = VeDbusService(
- "{}.{}.input{:02d}".format(base, self.dbus_name, gpio), bus=bus,
+ "{}.{}.{}".format(base, self.dbus_name, name), bus=bus,
register=False)
# Add objects required by ve-api
self.service.add_path('/Mgmt/ProcessName', __file__)
self.service.add_path('/Mgmt/ProcessVersion', VERSION)
self.service.add_path('/Mgmt/Connection', path)
- self.service.add_path('/DeviceInstance', gpio)
+ self.service.add_path('/DeviceInstance', instance)
self.service.add_path('/ProductId', self.product_id)
self.service.add_path('/ProductName', self.product_name)
self.service.add_path('/Connected', 1)
@@ -401,9 +417,11 @@ class Generator(PinAlarm):
_product_name = "Generator"
type_id = 9
translation = 5 # running, stopped
+ startStopService = 'com.victronenergy.generator.startstop0'
- def __init__(self, *args, **kwargs):
- super(Generator, self).__init__(*args, **kwargs)
+ def __init__(self, bus, base, path, gpio, settings):
+ super(Generator, self).__init__(bus, base, path, gpio, settings)
+ self._gpio = gpio
# Periodically rewrite the generator selection. The Multi may reset
# causing this to be lost, or a race condition on startup may cause
# it to not be set properly.
@@ -416,7 +434,15 @@ class Generator(PinAlarm):
services = [n for n in self.bus.list_names() if n.startswith(
'com.victronenergy.vebus.')]
for n in services:
- self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', None,
+ self.bus.call_async(n, '/Ac/Control/RemoteGeneratorSelected', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [v], None, None)
+ except dbus.exceptions.DBusException:
+ print ("DBus exception setting RemoteGeneratorSelected")
+ traceback.print_exc()
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [self._gpio], None, None)
+ self.bus.call_async(self.startStopService, '/DigitalInput/Running', 'com.victronenergy.BusItem',
'SetValue', 'v', [v], None, None)
except dbus.exceptions.DBusException:
print ("DBus exception setting RemoteGeneratorSelected")
@@ -432,7 +458,11 @@ class Generator(PinAlarm):
super(Generator, self).deactivate()
# When deactivating, reset the generator selection state
self.select_generator(0)
-
+ try:
+ self.bus.call_async(self.startStopService, '/DigitalInput/Input', 'com.victronenergy.BusItem',
+ 'SetValue', 'v', [0], None, None)
+ except dbus.exceptions.DBusException:
+ pass
# And kill the periodic job
GLib.source_remove(self._timer)
self._timer = None
@@ -482,6 +512,29 @@ class GenericIO(PinAlarm):
def dbusconnection():
return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
+def parse_config(conf):
+ f = open(conf)
+
+ tag = None
+ pins = []
+
+ for line in f:
+ cmd, arg = line.strip().split(maxsplit=1)
+
+ if cmd == 'tag':
+ tag = arg
+ continue
+
+ if cmd == 'input':
+ pth, label = arg.split(maxsplit=1)
+ label = label.strip('"')
+ pin = InputPin(tag + '_' + os.path.basename(pth), pth, label)
+ pins.append(pin)
+ continue
+
+ f.close()
+
+ return pins
def main():
parser = ArgumentParser(description=sys.argv[0])
@@ -491,7 +544,8 @@ def main():
parser.add_argument('--poll',
help='Use a different kind of polling. Options are epoll, dumb and debug',
default='epoll')
- parser.add_argument('inputs', nargs='+', help='Path to digital input')
+ parser.add_argument('--conf', action='append', default=[], help='Config file')
+ parser.add_argument('inputs', nargs='*', help='Path to digital input')
args = parser.parse_args()
PulseCounter = {
@@ -501,6 +555,9 @@ def main():
DBusGMainLoop(set_as_default=True)
+ ctlbus = dbusconnection()
+ ctlsvc = VeDbusService(args.servicebase + '.digitalinputs', bus=ctlbus, register=True)
+
# Keep track of enabled services
services = {}
inputs = dict(enumerate(args.inputs, 1))
@@ -521,15 +578,18 @@ def main():
def unregister_gpio(gpio):
print ("unRegistering GPIO {}".format(gpio))
- pulses.unregister(gpio)
- services[gpio].deactivate()
+ if pulses.registered(gpio):
+ pulses.unregister(gpio)
+ services[gpio].deactivate()
- def handle_setting_change(inp, setting, old, new):
+ def handle_setting_change(pin, setting, old, new):
# This handler may also be called if some attribute of a setting
# is changed, but not the value. Bail if the value is unchanged.
if old == new:
return
+ inp = pin.name
+
if setting == 'inputtype':
if new:
# Get current bus and settings objects, to be reused
@@ -540,6 +600,12 @@ def main():
if pulses.registered(inp):
unregister_gpio(inp)
+ # We only want 1 generator input at a time, so disable other inputs configured as generator.
+ for i in inputs:
+ if i != inp and services[i].settings['inputtype'] == 9 == new:
+ services[i].settings['inputtype'] = 0
+ unregister_gpio(i)
+
# Before registering the new input, reset its settings to defaults
settings['count'] = 0
settings['invert'] = 0
@@ -547,10 +613,12 @@ def main():
settings['alarm'] = 0
# Register it
- register_gpio(inputs[inp], inp, bus, settings)
+ register_gpio(pin.path, inp, bus, settings)
elif old:
# Input disabled
unregister_gpio(inp)
+
+ ctlsvc['/Devices/{}/Type'.format(inp)] = new
elif setting in ('rate', 'invert', 'alarm', 'invertalarm'):
services[inp].refresh()
elif setting == 'name':
@@ -560,11 +628,31 @@ def main():
# if it has changed.
v = int(new)
s = services[inp]
- if s.count != v:
+ if s.active and s.count != v:
s.count = v
s.refresh()
+ def change_type(sd, path, val):
+ if not 0 <= val < len(INPUTTYPES):
+ return False
+ sd['inputtype'] = val
+ return True
+
+ pins = []
+
for inp, pth in inputs.items():
+ pin = InputPin(inp, pth, 'Digital input {}'.format(inp))
+ pin.devid = os.path.basename(pth)
+ pin.devinstance = inp
+ pins.append(pin)
+
+ for conf in args.conf:
+ pins += parse_config(conf)
+
+ for pin in pins:
+ inp = pin.name
+ devid = pin.devid or pin.name
+ inst = 'digitalinput:{}'.format(pin.devinstance or 10)
supported_settings = {
'inputtype': ['/Settings/DigitalInput/{}/Type'.format(inp), 0, 0, len(INPUTTYPES)-1],
'rate': ['/Settings/DigitalInput/{}/Multiplier'.format(inp), 0.001, 0, 1.0],
@@ -573,10 +661,14 @@ def main():
'invertalarm': ['/Settings/DigitalInput/{}/InvertAlarm'.format(inp), 0, 0, 1],
'alarm': ['/Settings/DigitalInput/{}/AlarmSetting'.format(inp), 0, 0, 1],
'name': ['/Settings/DigitalInput/{}/CustomName'.format(inp), '', '', ''],
+ 'instance': ['/Settings/Devices/{}/ClassAndVrmInstance'.format(devid), inst, '', ''],
}
bus = dbusconnection()
- sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, inp), timeout=10)
- register_gpio(pth, inp, bus, sd)
+ sd = SettingsDevice(bus, supported_settings, partial(handle_setting_change, pin), timeout=10)
+ register_gpio(pin.path, inp, bus, sd)
+ ctlsvc.add_path('/Devices/{}/Label'.format(inp), pin.label)
+ ctlsvc.add_path('/Devices/{}/Type'.format(inp), sd['inputtype'],
+ writeable=True, onchangecallback=partial(change_type, sd))
def poll(mainloop):
from time import time
@@ -604,8 +696,8 @@ def main():
# Periodically save the counter
def save_counters():
- for inp in inputs:
- services[inp].save_count()
+ for svc in services.values():
+ svc.save_count()
return True
GLib.timeout_add(SAVEINTERVAL, save_counters)
diff --git a/FileSets/v3.50~25/startstop.py b/FileSets/v3.51~2/startstop.py
similarity index 85%
rename from FileSets/v3.50~25/startstop.py
rename to FileSets/v3.51~2/startstop.py
index 914916a6..244de097 100644
--- a/FileSets/v3.50~25/startstop.py
+++ b/FileSets/v3.51~2/startstop.py
@@ -3,15 +3,12 @@
#### GuiMods
#### This file has been modified to allow the generator running state derived from the generator digital input
-#### or the genset AC input
+#### Previous versions also used the genset AC input but this has been removed from this version with recent changes to stock code !!!!!
#### If the incoming generator state changes, the manual start state is updated
-#### time accumulation is suspended when the generator is not running
-#### A switch in the generator settings menucontrols whethter the incoming state affects manual start or time accumulaiton
+#### A switch in the generator settings menu controls whethter the incoming state affects manual start or time accumulaiton
#### It is now possible to start the generator manually and have it stop automatically based on the preset conditions
#### for automaitc start / stop
-#### A service interval timer was added so the accumulated run time does not need to be reset,
-#### providing total run time for the generator
-#### warm-up and cool-down periods have been modified in order to work well with an external transfer switch
+#### warm-up and cool-down periods have been modified in order to work with an external transfer switch
#### selecting grid or generator ahead of a MultiPlus input.
#### Search for #### GuiMods to find changes
@@ -266,18 +263,13 @@ def soc(self):
class StartStop(object):
_driver = None
def __init__(self, instance):
-#### GuiMods
+#### GuiMods #### TODO: check if any of these are needed
logging.info ("GuiMods version of startstop.py")
- self._currentTime = 0
+ self._currentTime = self._get_monotonic_seconds()
self._last_update_mtime = 0
self._accumulatedRunTime = 0
- self._digitalInputTypeObject = None
- self._generatorInputStateObject = 0
- self._lastState = 0
- self._externalOverride = False
+ self._lastIsRunning = False
self._externalOverrideDelay = 99
- self._lastExternalOverride = False
- self._searchDelay = 99
self._linkToExternalState = False
#### GuiMods warm-up / cool-down
self._warmUpEndTime = 0
@@ -286,6 +278,7 @@ def __init__(self, instance):
self._ac2isIgnored = False
self._activeAcInIsIgnored = False
self._acInIsGenerator = False
+#### end GuiMods
self._dbusservice = None
self._settings = None
@@ -293,6 +286,7 @@ def __init__(self, instance):
self._remoteservice = None
self._name = None
self._enabled = False
+ self._generator_running = False
self._useGensetHours = False # Sync with genset operatinghours.
self._instance = instance
@@ -301,15 +295,23 @@ def __init__(self, instance):
self._testrun_soc_retries = 0
self._last_counters_check = 0
- self._starttime = 0
+ # Two different starttime values.
+ # starttime_fb is set by the modules (relay.py, genset.py) and will be set to the current time when
+ # the feedback mechanism (digital input / `/StatusCode`) indicates that the generator is running.
+ # The other one is set by startstop when commanding the module to start the generator and is used by the
+ # warm-up mechanism to ensure warm-up finishes without needing feedback that the generator has actually started.
+ # If there is no feedback mechanism in place, the values will be equal as the module will call '_generator_started()`
+ # right after receiving the start command from startstop.
+ self._starttime_fb = 0 # Starttime of the generator as reported by the feedback mechanism (e.g., digital input), if present
+ self._starttime = 0 # Starttime of the generator, maintained by startstop. Not influenced by feedback mechanism.
self._stoptime = 0 # Used for cooldown
self._manualstarttimer = 0
self._last_runtime_update = 0
self._timer_runnning = 0
# The installer left autostart disabled
- self._autostart_last_time = self._get_monotonic_seconds()
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
+ self._autostart_last_time = self._currentTime
+ self._remote_start_mode_last_time = self._currentTime
# Manual battery service selection is deprecated in favour
@@ -395,10 +397,14 @@ def _create_service(self):
self._dbusservice.add_path('/ServiceCounterReset', value=None, writeable=True, onchangecallback=self._reset_service_counter)
# Publish what service we're controlling, and the productid
self._dbusservice.add_path('/GensetService', value=self._remoteservice)
+ self._dbusservice.add_path('/GensetServiceType',
+ value=self._remoteservice.split('.')[2] if self._remoteservice is not None else None)
self._dbusservice.add_path('/GensetInstance',
value=self._dbusmonitor.get_value(self._remoteservice, '/DeviceInstance'))
self._dbusservice.add_path('/GensetProductId',
value=self._dbusmonitor.get_value(self._remoteservice, '/ProductId'))
+ self._dbusservice.add_path('/DigitalInput/Running', value=None, writeable=True, onchangecallback=self._running_by_digital_input)
+ self._dbusservice.add_path('/DigitalInput/Input', value=None, writeable=True, onchangecallback=self._running_by_digital_input)
self._dbusservice.register()
# We need to set the values after creating the paths to trigger the 'onValueChanged' event for the gui
@@ -426,20 +432,24 @@ def _create_service(self):
self._dbusservice['/ServiceInterval'] = int(self._settings['serviceinterval'])
self._dbusservice['/ServiceCounter'] = None
self._dbusservice['/ServiceCounterReset'] = 0
+ self._dbusservice['/DigitalInput/Running'] = 0
+ self._dbusservice['/DigitalInput/Input'] = 0
# When this startstop instance controls a genset which reports operatinghours, make sure to synchronize with that.
self._useGensetHours = self._dbusmonitor.get_value(self._remoteservice, '/Engine/OperatingHours', None) is not None
#### GuiMods
# generator input running state
- self._dbusservice.add_path('/GeneratorRunningState', value=None)
# external override active
self._dbusservice.add_path('/ExternalOverride', value=None)
- self._dbusservice['/GeneratorRunningState'] = "?"
self._dbusservice['/ExternalOverride'] = False
self._ignoreAutoStartCondition = False
-
+
+ @property
+ def _is_running(self):
+ return self._generator_running
+
@property
def capabilities(self):
return self._dbusservice['/Capabilities']
@@ -462,7 +472,7 @@ def enable(self):
# flag to a sane value on startup.
if self._settings['cooldowntime'] > 0 or \
self._settings['warmuptime'] > 0:
- self._set_ignore_ac(False) ###########
+ self._set_ignore_ac(False)
self._enabled = True
def disable(self):
@@ -581,16 +591,15 @@ def tick(self):
self._check_remote_status()
#### GuiMods
self._linkToExternalState = self._settings['linkManualStartToExternal'] == 1
- self._processGeneratorRunDetection ()
+ self.syncManualRunToExternalState ()
self._evaluate_startstop_conditions()
self._evaluate_autostart_disabled_alarm()
self._detect_generator_at_acinput()
if self._dbusservice['/ServiceCounterReset'] == 1:
self._dbusservice['/ServiceCounterReset'] = 0
-
+
#### GuiMods warm-up / cool-down
- state = self._dbusservice['/State']
# shed load for active generator input in warm-up and cool-down
# note that external transfer switch might change the state of on generator
@@ -598,6 +607,7 @@ def tick(self):
# restore load for sources no longer in use or if state is not in warm-up/cool-down
# restoring load is delayed 1following end of cool-down
# to allow the generator to actually stop producing power
+ state = self._dbusservice['/State']
if state in (States.WARMUP, States.COOLDOWN, States.STOPPING):
self._set_ignore_ac (True)
else:
@@ -642,9 +652,7 @@ def _evaluate_startstop_conditions(self):
self._last_counters_check = today
self._update_accumulated_time()
- # Update current and accumulated runtime.
-#### GuiMods
- self._accumulateRunTime ()
+ self._update_runtime()
#### GuiMods
# A negative /ManualStartTimer is used by the GUI to signal the generator should start now
@@ -662,6 +670,7 @@ def _evaluate_startstop_conditions(self):
if self._evaluate_manual_start():
startbycondition = 'manual'
start = True
+#### end GuiMods
# Conditions will only be evaluated if the autostart functionality is enabled
if self._settings['autostart'] == 1:
@@ -731,12 +740,25 @@ def _evaluate_startstop_conditions(self):
elif (self._dbusservice['/Runtime'] >= self._settings['minimumruntime'] * 60
or activecondition == 'manual') or self._dbusservice['/ExternalOverride']:
self._stop_generator()
+#### end GuiMods
+
+ def _update_runtime(self, just_stopped=False):
+ # Update current and accumulated runtime.
+ # By performance reasons, accumulated runtime is only updated
+ # once per 60s. When the generator stops is also updated.
+ if self._is_running or just_stopped:
+ mtime = monotonic_time.monotonic_time().to_seconds_double()
+ if (mtime - self._starttime_fb) - self._last_runtime_update >= 60 or just_stopped:
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
+ self._update_accumulated_time()
+ elif self._last_runtime_update == 0:
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
def _evaluate_autostart_disabled_alarm(self):
if self._settings['autostartdisabledalarm'] == 0:
- self._autostart_last_time = self._get_monotonic_seconds()
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
+ self._autostart_last_time = self._currentTime
+ self._remote_start_mode_last_time = self._currentTime
if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
self._dbusservice['/Alarms/AutoStartDisabled'] = 0
if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
@@ -745,30 +767,28 @@ def _evaluate_autostart_disabled_alarm(self):
# GX auto start/stop alarm
if self._settings['autostart'] == 1:
- self._autostart_last_time = self._get_monotonic_seconds()
+ self._autostart_last_time = self._currentTime
if self._dbusservice['/Alarms/AutoStartDisabled'] != 0:
self._dbusservice['/Alarms/AutoStartDisabled'] = 0
else:
- timedisabled = self._get_monotonic_seconds() - self._autostart_last_time
+ timedisabled = self._currentTime - self._autostart_last_time
if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/AutoStartDisabled'] != 2:
self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
self._dbusservice['/Alarms/AutoStartDisabled'] = 2
# Genset remote start mode alarm
if self.get_error() != Errors.REMOTEDISABLED:
- self._remote_start_mode_last_time = self._get_monotonic_seconds()
+ self._remote_start_mode_last_time = self._currentTime
if self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 0:
self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 0
else:
- timedisabled = self._get_monotonic_seconds() - self._remote_start_mode_last_time
+ timedisabled = self._currentTime - self._remote_start_mode_last_time
if timedisabled > AUTOSTART_DISABLED_ALARM_TIME and self._dbusservice['/Alarms/RemoteStartModeDisabled'] != 2:
self.log_info("Autostart was left for more than %i seconds, triggering alarm." % int(timedisabled))
self._dbusservice['/Alarms/RemoteStartModeDisabled'] = 2
-
#### GuiMods warm-up / cool-down - rewrote so acInIsGenerator is updated even if alarm is disabled
def _detect_generator_at_acinput(self):
-#### GuiMods warm-up / cool-down
self._acInIsGenerator = False # covers all conditions that result in a return
state = self._dbusservice['/State']
@@ -814,7 +834,7 @@ def _detect_generator_at_acinput(self):
self._acpower_inverter_input['unabletostart'] = True
self._dbusservice['/Alarms/NoGeneratorAtAcIn'] = 2
self.log_info('Generator not detected at inverter AC input, triggering alarm')
-
+#### end GuiMods
def _reset_acpower_inverter_input(self, clear_error=True):
if self._acpower_inverter_input['timeout'] != 0:
@@ -897,6 +917,10 @@ def _evaluate_condition(self, condition):
# first check if start value should be greater than stop value and then compare
start_is_greater = startvalue > stopvalue
+ # When the condition is already reached only the stop value can set it to False
+ start = condition['reached'] or (value >= startvalue if start_is_greater else value <= startvalue)
+ stop = value <= stopvalue if start_is_greater else value >= stopvalue
+
#### GuiMods
stop = value <= stopvalue if start_is_greater else value >= stopvalue
# when starting manually and stopping based on auto stop values,
@@ -907,6 +931,7 @@ def _evaluate_condition(self, condition):
else:
# When the condition is already reached only the stop value can set it to False
start = condition['reached'] or (value >= startvalue if start_is_greater else value <= startvalue)
+#### end GuiMods
# Timed conditions must start/stop after the condition has been reached for a minimum
# time.
@@ -940,7 +965,7 @@ def _evaluate_manual_start(self):
# If /ManualStartTimer has a value greater than zero will use it to set a stop timer.
# If no timer is set, the generator will not stop until the user stops it manually.
# Once started by manual start, each evaluation the timer is decreased
-#### GuiMods
+#### GuiMods - change test to > 0 from != 0 to allow for start now / auto stop
if self._dbusservice['/ManualStartTimer'] > 0:
self._manualstarttimer += time.time() if self._manualstarttimer == 0 else 0
self._dbusservice['/ManualStartTimer'] -= int(time.time()) - int(self._manualstarttimer)
@@ -992,8 +1017,8 @@ def _evaluate_testrun_condition(self):
return False
start = False
- # If the accumulated runtime during the tes trun interval is greater than '/TestRunIntervalRuntime'
- # the tes trun must be skipped
+ # If the accumulated runtime during the test run interval is greater than '/TestRunIntervalRuntime'
+ # the test run must be skipped
needed = (self._settings['testrunskipruntime'] > self._dbusservice['/TestRunIntervalRuntime']
or self._settings['testrunskipruntime'] == 0)
self._dbusservice['/SkipTestRun'] = int(not needed)
@@ -1089,7 +1114,7 @@ def _update_accumulated_time(self, gensetHours=None):
if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING):
mtime = monotonic_time.monotonic_time().to_seconds_double()
- self._dbusservice['/Runtime'] = int(mtime - self._starttime)
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
self._last_runtime_update = seconds
@@ -1243,6 +1268,7 @@ def _start_generator(self, condition):
# already running. When differs, the RunningByCondition is updated
running = state in (States.WARMUP, States.COOLDOWN, States.STOPPING, States.RUNNING)
if not (running and remote_running): # STOPPED, ERROR
+ # There is an option to skip warm-up for the inverteroverload condition.
#### GuiMods warm-up / cool-down
self.log_info('Starting generator by %s condition' % condition)
# if there is a warmup time specified, always go through warm-up state
@@ -1278,11 +1304,9 @@ def _start_generator(self, condition):
% (self._dbusservice['/RunningByCondition'], condition))
#### end GuiMods warm-up / cool-down
-
self._dbusservice['/RunningByCondition'] = condition
self._dbusservice['/RunningByConditionCode'] = RunningConditions.lookup(condition)
-
def _stop_generator(self):
state = self._dbusservice['/State']
remote_running = self._get_remote_switch_state()
@@ -1330,7 +1354,6 @@ def _stop_generator(self):
self._dbusservice['/State'] = state
#### end GuiMods warm-up / cool-down
-
@property
def _ac1_is_generator(self):
return self._dbusmonitor.get_value('com.victronenergy.settings',
@@ -1341,14 +1364,16 @@ def _ac2_is_generator(self):
return self._dbusmonitor.get_value('com.victronenergy.settings',
'/Settings/SystemSetup/AcInput2') == 2
- def _set_ignore_ac(self, ignore):
- # This is here so the Multi/Quattro can be told to disconnect AC-in,
- # so that we can do warm-up and cool-down.
#### GuiMods warm-up / cool-down
# stock code does not handle changes in the input type
# which could happen with an external transfer switch
# doing things this way should handle it
+ def _set_ignore_ac(self, ignore):
+ # This is here so the Multi/Quattro can be told to disconnect AC-in,
+ # so that we can do warm-up and cool-down.
+ if self._vebusservice is None:
+ return
self._activeAcInIsIgnored = ignore
ignore1 = False
ignore2 = False
@@ -1374,16 +1399,26 @@ def _set_ignore_ac(self, ignore):
self._ac2isIgnored = ignore2
#### end GuiMods warm-up / cool-down
-
def _update_remote_switch(self):
# Engine should be started in these states
v = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN)
self._set_remote_switch_state(dbus.Int32(v, variant_level=1))
-#### GuiMods
- if v == True:
- self.log_info ("updating remote switch to running")
- else:
- self.log_info ("updating remote switch to stopped")
+
+ def _running_by_digital_input(self, path, value):
+ return
+
+ def _generator_started(self):
+ if (not self._generator_running):
+ self._starttime_fb = monotonic_time.monotonic_time().to_seconds_double()
+ self._generator_running = True
+
+ def _generator_stopped(self):
+ if (self._generator_running):
+ self._generator_running = False
+ self._update_runtime(just_stopped=True)
+ self._dbusservice['/Runtime'] = 0
+ self._starttime_fb = 0
+ self._last_runtime_update = 0
def _get_remote_switch_state(self):
raise Exception('This function should be overridden')
@@ -1407,12 +1442,9 @@ def _create_settings(self, *args, **kwargs):
def _create_dbus_service(self):
return create_dbus_service(self._instance)
-
#### GuiMods
-
# this function connects the generator digital input (if any)
-# OR the generator AC input detection
-# to the generator /ManualStart and updates dbus paths used by the GUI
+# to the generator /ManualStart
#
# if the generator digital input changes from stopped to running
# AND no run conditions are active, a manual start is innitiated
@@ -1420,177 +1452,31 @@ def _create_dbus_service(self):
# if the generator digital input changes from running to stopped
# AND a manual start is active, a manual stop is innitiated
#
-# /GeneratorRunningState provides the input running state from the digital input to the GUI
-# R = running
-# S = stopped
-# ? = unknown (no digital input found)
-#
# /ExternalOverride is used by the GUI to alert the user when there is a conflict
-# between the generator running state and the state Venus
-# /ExternalOverride is True if /GeneratorRunningState is S
-# AND the /RunningCondition is not stopped (which includes a manual run)
+# between the running state reported by the generator and the expected state
+# /ExternalOverride is True if the states differ
# activation is delayed 5 seconds to allow transitions to settle
-#
-# we must first find the geneator digital input, if it exists at all
-# we serche all dBus services looking for a digital input with type generator (9)
-# the search only occurs every 10 seconds
-#
- def _processGeneratorRunDetection (self):
- TheBus = dbus.SystemBus()
- generatorState = self._dbusservice['/State']
- try:
- # current input service is no longer valid
- # search for a new one only every 10 seconds to avoid unnecessary processing
- if (self._digitalInputTypeObject == None or self._digitalInputTypeObject.GetValue() != 9) and self._searchDelay > 10:
- newInputService = ""
- for service in TheBus.list_names():
- # found a digital input servic, now check the type
- if service.startswith ("com.victronenergy.digitalinput"):
- self._digitalInputTypeObject = TheBus.get_object (service, '/Type')
- # found it!
- if self._digitalInputTypeObject.GetValue() == 9:
- newInputService = service
- break
-
- # found new service - get objects for use later
- if newInputService != "":
- self.log_info ("Found generator digital input service at %s" % newInputService)
- self._generatorInputStateObject = TheBus.get_object(newInputService, '/State')
- else:
- if self._generatorInputStateObject != None:
- self.log_info ("Generator digital input service NOT found")
- self._generatorInputStateObject = None
- self._digitalInputTypeObject = None
- self._searchDelay = 0 # start delay timer
-
- # if serch delay timer is active, increment it now
- if self._searchDelay <= 10:
- self._searchDelay += 1
-
-
- # collect generator input states
- inputState = '?'
- # if generator digital input is present, use that
- if self._generatorInputStateObject != None:
- inputState = self._generatorInputStateObject.GetValue ()
- if inputState == 10:
- inputState = 'R'
- elif inputState == 11:
- inputState = 'S'
- # otherwise use generator AC input to determine running state
- # use frequency as the test for generator running
- elif self._ac1_is_generator or self._ac2_is_generator:
- try:
- if self._dbusmonitor.get_value (SYSTEM_SERVICE, '/Ac/Genset/Frequency') > 20:
- inputState = 'R'
- else:
- inputState = 'S'
- except:
- pass
-
- # update /GeneratorRunningState
- if inputState != self._lastState:
- self._dbusservice['/GeneratorRunningState'] = inputState
-
- # forward input state changes to /ManualStart
- if self._linkToExternalState:
- if inputState == "R" and generatorState == States.STOPPED:
- self.log_info ("generator was started externally - syncing ManualStart state")
- self._dbusservice['/ManualStart'] = 1
- elif inputState == "S" and self._dbusservice['/ManualStart'] == 1 \
- and generatorState in (States.RUNNING, States.WARMUP, States.COOLDOWN):
- self.log_info ("generator was stopped externally - syncing ManualStart state")
- self._dbusservice['/ManualStart'] = 0
-
- # update /ExternalOverride
- if inputState == "S" and self._linkToExternalState and generatorState == States.RUNNING:
- if self._externalOverrideDelay > 5:
- self._externalOverride = True
- else:
- self._externalOverrideDelay += 1
+ def syncManualRunToExternalState (self):
+ internalRun = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN)
+ externalRun = self._is_running
+ # forward input state changes to /ManualStart
+ if self._linkToExternalState and externalRun != self._lastIsRunning:
+ if externalRun and not internalRun:
+ self.log_info ("generator was started externally - syncing ManualStart state")
+ self._dbusservice['/ManualStart'] = 1
+ elif not externalRun and internalRun and self._dbusservice['/ManualStart'] == 1:
+ self.log_info ("generator was stopped externally - syncing ManualStart state")
+ self._dbusservice['/ManualStart'] = 0
+ self._lastIsRunning = externalRun
+
+ # update ExternalOverride
+ if externalRun != internalRun:
+ if self._externalOverrideDelay > 5:
+ self._dbusservice['/ExternalOverride'] = 1
else:
- self._externalOverride = False
- self._externalOverrideDelay = 0
-
- if self._externalOverride != self._lastExternalOverride:
- self._dbusservice['/ExternalOverride'] = self._externalOverride
- self._lastExternalOverride = self._externalOverride
-
- except dbus.DBusException:
- self.log_info ("dbus exception - generator digital input no longer valid")
- self._generatorInputStateObject = None
- self._digitalInputTypeObject = None
- inputState = 0
-
- self._lastState = inputState
-
-
-#
-# control the accumulaiton of run time based on generator input Running state
-# if the internal state is RUNNING run time is accumulated in self._accumulatedRunTime
-# run time is accumulated if the generator's running state is known to be running or
-# if the generator running state can't be determined
-# the accumulated time dBus parameter and daily and total time accumulators are updated
-# only once everh 60 seconds to minimize processor load
-# if the internal state is STOPPED, one last dBus, daily and total time updates are done
-# then the current time accumulator is cleared
-
- def _accumulateRunTime (self):
-
- # grab running state from dBus once, use it many timed below
-
- if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING):
- internalRun = True
- else:
- internalRun = False
-
- # if internal state is running, accumulate time if generator is running
- if internalRun:
- accumuateTime = True
- # start new accumulation if not done prevously
- if self._last_accumulate_time == 0:
- self._last_accumulate_time = self._currentTime
-
- # if link to external state is enabled, don't accumulate time if running state is stopped
- # (accumulate if R or ?)
- if self._linkToExternalState:
- try:
- if self._dbusservice['/GeneratorRunningState'] == 'S':
- accumuateTime = False
-
- # if no Forwarder service, allow accumulation
- except dbus.DBusException:
- self.log_info ("dBus exception in startstop.py")
-
- # internal state STOPPED so don't add new time to the accumulation
- # but there may be time already accumulated that needs to be added to daily and total accumulations
+ self._externalOverrideDelay += 1
else:
- accumuateTime = False
-
- # accumulate run time if we passed all the tests above
- if accumuateTime:
- self._accumulatedRunTime += self._currentTime - self._last_accumulate_time
- self._last_accumulate_time = self._currentTime
-
- # dbus and settings updates trigger time-intensive processing so only do this once every 60 seconds
- doUpdate = False
- if internalRun:
- if self._currentTime - self._last_update_mtime >= 60:
- doUpdate = True
- self._last_update_mtime = self._currentTime
- # it is also done one last time when state is no longer RUNNING
- elif self._last_update_mtime != 0:
- doUpdate = True
-
- if doUpdate:
- self._update_accumulated_time()
-
- # stopped - clear the current time accumulator
- if internalRun == False:
- self._last_update_mtime = 0
- self._accumulatedRunTime = 0
- self._last_accumulate_time = 0
-
- self._dbusservice['/Runtime'] = int(self._accumulatedRunTime)
+ self._dbusservice['/ExternalOverride'] = 0
+ self._externalOverrideDelay = 0
#### end GuiMods
diff --git a/FileSets/v3.50~25/startstop.py.orig b/FileSets/v3.51~2/startstop.py.orig
similarity index 94%
rename from FileSets/v3.50~25/startstop.py.orig
rename to FileSets/v3.51~2/startstop.py.orig
index 9595f6f8..eb964b84 100644
--- a/FileSets/v3.50~25/startstop.py.orig
+++ b/FileSets/v3.51~2/startstop.py.orig
@@ -258,6 +258,7 @@ class StartStop(object):
self._remoteservice = None
self._name = None
self._enabled = False
+ self._generator_running = False
self._useGensetHours = False # Sync with genset operatinghours.
self._instance = instance
@@ -266,7 +267,15 @@ class StartStop(object):
self._testrun_soc_retries = 0
self._last_counters_check = 0
- self._starttime = 0
+ # Two different starttime values.
+ # starttime_fb is set by the modules (relay.py, genset.py) and will be set to the current time when
+ # the feedback mechanism (digital input / `/StatusCode`) indicates that the generator is running.
+ # The other one is set by startstop when commanding the module to start the generator and is used by the
+ # warm-up mechanism to ensure warm-up finishes without needing feedback that the generator has actually started.
+ # If there is no feedback mechanism in place, the values will be equal as the module will call '_generator_started()`
+ # right after receiving the start command from startstop.
+ self._starttime_fb = 0 # Starttime of the generator as reported by the feedback mechanism (e.g., digital input), if present
+ self._starttime = 0 # Starttime of the generator, maintained by startstop. Not influenced by feedback mechanism.
self._stoptime = 0 # Used for cooldown
self._manualstarttimer = 0
self._last_runtime_update = 0
@@ -360,10 +369,14 @@ class StartStop(object):
self._dbusservice.add_path('/ServiceCounterReset', value=None, writeable=True, onchangecallback=self._reset_service_counter)
# Publish what service we're controlling, and the productid
self._dbusservice.add_path('/GensetService', value=self._remoteservice)
+ self._dbusservice.add_path('/GensetServiceType',
+ value=self._remoteservice.split('.')[2] if self._remoteservice is not None else None)
self._dbusservice.add_path('/GensetInstance',
value=self._dbusmonitor.get_value(self._remoteservice, '/DeviceInstance'))
self._dbusservice.add_path('/GensetProductId',
value=self._dbusmonitor.get_value(self._remoteservice, '/ProductId'))
+ self._dbusservice.add_path('/DigitalInput/Running', value=None, writeable=True, onchangecallback=self._running_by_digital_input)
+ self._dbusservice.add_path('/DigitalInput/Input', value=None, writeable=True, onchangecallback=self._running_by_digital_input)
self._dbusservice.register()
# We need to set the values after creating the paths to trigger the 'onValueChanged' event for the gui
@@ -391,10 +404,16 @@ class StartStop(object):
self._dbusservice['/ServiceInterval'] = int(self._settings['serviceinterval'])
self._dbusservice['/ServiceCounter'] = None
self._dbusservice['/ServiceCounterReset'] = 0
+ self._dbusservice['/DigitalInput/Running'] = 0
+ self._dbusservice['/DigitalInput/Input'] = 0
# When this startstop instance controls a genset which reports operatinghours, make sure to synchronize with that.
self._useGensetHours = self._dbusmonitor.get_value(self._remoteservice, '/Engine/OperatingHours', None) is not None
+ @property
+ def _is_running(self):
+ return self._generator_running
+
@property
def capabilities(self):
return self._dbusservice['/Capabilities']
@@ -566,16 +585,7 @@ class StartStop(object):
self._last_counters_check = today
self._update_accumulated_time()
- # Update current and accumulated runtime.
- # By performance reasons, accumulated runtime is only updated
- # once per 60s. When the generator stops is also updated.
- if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING):
- mtime = monotonic_time.monotonic_time().to_seconds_double()
- if (mtime - self._starttime) - self._last_runtime_update >= 60:
- self._update_accumulated_time()
- elif self._last_runtime_update == 0:
- self._dbusservice['/Runtime'] = int(mtime - self._starttime)
-
+ self._update_runtime()
if self._evaluate_manual_start():
startbycondition = 'manual'
@@ -636,12 +646,25 @@ class StartStop(object):
if self._errorstate:
return
+ mtime = monotonic_time.monotonic_time().to_seconds_double()
if start:
self._start_generator(startbycondition)
- elif (self._dbusservice['/Runtime'] >= self._settings['minimumruntime'] * 60
- or activecondition == 'manual'):
+ elif (int(mtime - self._starttime) >= self._settings['minimumruntime'] * 60
+ or activecondition == 'manual'):
self._stop_generator()
+ def _update_runtime(self, just_stopped=False):
+ # Update current and accumulated runtime.
+ # By performance reasons, accumulated runtime is only updated
+ # once per 60s. When the generator stops is also updated.
+ if self._is_running or just_stopped:
+ mtime = monotonic_time.monotonic_time().to_seconds_double()
+ if (mtime - self._starttime_fb) - self._last_runtime_update >= 60 or just_stopped:
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
+ self._update_accumulated_time()
+ elif self._last_runtime_update == 0:
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
+
def _evaluate_autostart_disabled_alarm(self):
if self._settings['autostartdisabledalarm'] == 0:
@@ -879,8 +902,8 @@ class StartStop(object):
return False
start = False
- # If the accumulated runtime during the tes trun interval is greater than '/TestRunIntervalRuntime'
- # the tes trun must be skipped
+ # If the accumulated runtime during the test run interval is greater than '/TestRunIntervalRuntime'
+ # the test run must be skipped
needed = (self._settings['testrunskipruntime'] > self._dbusservice['/TestRunIntervalRuntime']
or self._settings['testrunskipruntime'] == 0)
self._dbusservice['/SkipTestRun'] = int(not needed)
@@ -976,7 +999,7 @@ class StartStop(object):
if self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN, States.STOPPING):
mtime = monotonic_time.monotonic_time().to_seconds_double()
- self._dbusservice['/Runtime'] = int(mtime - self._starttime)
+ self._dbusservice['/Runtime'] = int(mtime - self._starttime_fb)
self._last_runtime_update = seconds
@@ -1140,6 +1163,7 @@ class StartStop(object):
self._update_remote_switch()
self._starttime = monotonic_time.monotonic_time().to_seconds_double()
+
self.log_info('Starting generator by %s condition' % condition)
else: # WARMUP, COOLDOWN, RUNNING, STOPPING
if state == States.WARMUP:
@@ -1194,19 +1218,16 @@ class StartStop(object):
# All other possibilities are handled now. Cooldown is over or not
# configured and we waited for the generator to shut down.
- self._dbusservice['/State'] = States.STOPPED
- self._update_remote_switch()
- self._set_ignore_ac(False)
self.log_info('Stopping generator that was running by %s condition' %
str(self._dbusservice['/RunningByCondition']))
self._dbusservice['/RunningByCondition'] = ''
self._dbusservice['/RunningByConditionCode'] = RunningConditions.Stopped
- self._update_accumulated_time()
- self._starttime = 0
- self._dbusservice['/Runtime'] = 0
+ self._dbusservice['/State'] = States.STOPPED
+ self._update_remote_switch()
+ self._set_ignore_ac(False)
self._dbusservice['/ManualStartTimer'] = 0
self._manualstarttimer = 0
- self._last_runtime_update = 0
+ self._starttime = 0
@property
def _ac1_is_generator(self):
@@ -1232,6 +1253,22 @@ class StartStop(object):
v = self._dbusservice['/State'] in (States.RUNNING, States.WARMUP, States.COOLDOWN)
self._set_remote_switch_state(dbus.Int32(v, variant_level=1))
+ def _running_by_digital_input(self, path, value):
+ return
+
+ def _generator_started(self):
+ if (not self._generator_running):
+ self._starttime_fb = monotonic_time.monotonic_time().to_seconds_double()
+ self._generator_running = True
+
+ def _generator_stopped(self):
+ if (self._generator_running):
+ self._generator_running = False
+ self._update_runtime(just_stopped=True)
+ self._dbusservice['/Runtime'] = 0
+ self._starttime_fb = 0
+ self._last_runtime_update = 0
+
def _get_remote_switch_state(self):
raise Exception('This function should be overridden')
diff --git a/ReadMe b/ReadMe
index fb0ae023..10094af7 100644
--- a/ReadMe
+++ b/ReadMe
@@ -1,3 +1,11 @@
+NOTE: starting with firmware v3.50, the "New UI" (aka gui-v2) is available for use
+ For GuiMods, the following opitons must be configured via Classic UI but will continue to function after switching
+ back to New UI:
+ configutaiton of the External transfer switch digital input
+ syncronizing the generator manual run to the generator digital input
+ PackageManager menus are also available only in Classic UI
+
+
NOTE: Support for firmware prior to v3.10 has been dropped starting with GuiMods v10.56
if you are running older versions, change the branch/tag to preV3.10support
for any packages you wish to run on that firmware
diff --git a/changes b/changes
index 02a090ae..634508bb 100644
--- a/changes
+++ b/changes
@@ -1,3 +1,9 @@
+v10.69:
+ add support for v3.50 and v3.51~2
+ removed support for v3.50 beta versions
+ revert to GuiMods v10.68 to support those
+ Note: there is no support for New UI but config possible in Classic UI
+
v10.68:
improve contrast in Relay Overview in dark mode
diff --git a/version b/version
index 1d454668..5c2ef3e0 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-v10.68
+v10.69~2\\