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\\