From 6355d538f8152a8cb0b494423f6cee1b60d018cb Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Tue, 2 Nov 2021 22:08:01 +0100 Subject: [PATCH 001/201] =?UTF-8?q?SoC=20Beschreibungstext=20auf=20=C3=9Cb?= =?UTF-8?q?erschreiten=20angepasst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/settings/pvconfig.html | 4 ++-- web/settings/settings.php | 8 ++++---- web/settings/sofortconfig.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index b8091f1fc4..cbd4006118 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -332,7 +332,7 @@

Einstellungen für PV-Laden

Parameter in Prozent [%] für den Maximal-SoC am Ladepunkt 1 im Modus PV-Laden. - Definiert einen EV-Maximal-SoC, bis zu dem höchstens geladen wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. + Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. @@ -364,7 +364,7 @@

Einstellungen für PV-Laden

Parameter in Prozent [%] für den Maximal-SoC am Ladepunkt 2 im Modus PV-Laden. - Definiert einen EV-Maximal-SoC, bis zu dem höchstens geladen wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. + Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. diff --git a/web/settings/settings.php b/web/settings/settings.php index 71cb0ef5db..69ca6ef9b7 100644 --- a/web/settings/settings.php +++ b/web/settings/settings.php @@ -1117,7 +1117,7 @@ function visibility_schieflastaktiv() { - Wenn SoC Modul vorhanden wird Nachts bis xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Sonntag Abend bis Freitag Morgen aktiv. + Wenn SoC Modul vorhanden wird Nachts bis zur Überschreitung von xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Sonntag Abend bis Freitag Morgen aktiv.
@@ -1129,7 +1129,7 @@ function visibility_schieflastaktiv() {
- Wenn SoC Modul vorhanden wird Nachts bis xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Freitag Morgen bis Sonntag Abend aktiv. + Wenn SoC Modul vorhanden wird Nachts bis zur Überschreitung von xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Freitag Morgen bis Sonntag Abend aktiv.
@@ -1303,7 +1303,7 @@ function generateMorningChargeDayOptions($dayName, $dayShortcut) { - Wenn SoC Modul vorhanden wird Nachts bis xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Sonntag Abend bis Freitag Morgen aktiv. + Wenn SoC Modul vorhanden wird Nachts in dem angegebenen Zeitfenster bis zur Überschreitung von xx% SoC geladen. Das SoC Fenster is von von Sonntag Abend bis Freitag Morgen aktiv.
@@ -1315,7 +1315,7 @@ function generateMorningChargeDayOptions($dayName, $dayShortcut) {
- Wenn SoC Modul vorhanden wird Nachts bis xx% SoC geladen in dem angegebenen Zeitfenster. Das SoC Fenster is von von Freitag Morgen bis Sonntag Abend aktiv. + Wenn SoC Modul vorhanden wird Nachts in dem angegebenen Zeitfenster bis zur Überschreitung von xx% SoC geladen. Das SoC Fenster is von von Freitag Morgen bis Sonntag Abend aktiv.
diff --git a/web/settings/sofortconfig.php b/web/settings/sofortconfig.php index 8c722a5a73..4b949ab28d 100644 --- a/web/settings/sofortconfig.php +++ b/web/settings/sofortconfig.php @@ -145,7 +145,7 @@ - Parameter in Prozent [%] für die Lademengenbegrenzung im Modus Sofortladen. Definiert den EV-SoC, auf den der Ladevorgang begrenzt werden soll. + Parameter in Prozent [%] für die Lademengenbegrenzung im Modus Sofortladen. Definiert den EV-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Dieser Parameter kann auf der Hauptseite der openWB per Sofortzugriff im Modus Sofortladen jederzeit geändert werden. From dc181dfcd09a81dda0c582c4a8028112297fb6e7 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Wed, 3 Nov 2021 12:05:41 +0100 Subject: [PATCH 002/201] soc update --- web/settings/pvconfig.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index cbd4006118..5b5aaeecce 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -293,7 +293,7 @@

Einstellungen für PV-Laden

- Parameter in Prozent [%] für den Maximal-SoC am LP1 im Modus PV-Laden. Definiert einen EV-Maximal-SoC, bis zu dem höchstens geladen wird. + Parameter in Prozent [%] für den Maximal-SoC am LP1 im Modus PV-Laden. Definiert einen EV-Maximal-SoC, Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. @@ -332,7 +332,7 @@

Einstellungen für PV-Laden

Parameter in Prozent [%] für den Maximal-SoC am Ladepunkt 1 im Modus PV-Laden. - Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. + Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehere Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. @@ -364,7 +364,7 @@

Einstellungen für PV-Laden

Parameter in Prozent [%] für den Maximal-SoC am Ladepunkt 2 im Modus PV-Laden. - Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehree Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. + Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. Diese Funktion ist nur dafür gedacht wenn mehrere Ladepunkte aktiv sind. Setzen auf 100% deaktiviert sie. From a774ae4976a10e98e745e068436ee3fb1c2718db Mon Sep 17 00:00:00 2001 From: Martin Rinas Date: Sat, 6 Nov 2021 12:03:47 +0100 Subject: [PATCH 003/201] fix typo --- web/settings/pvconfig.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index 5b5aaeecce..0d3f59508f 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -293,7 +293,7 @@

Einstellungen für PV-Laden

- Parameter in Prozent [%] für den Maximal-SoC am LP1 im Modus PV-Laden. Definiert einen EV-Maximal-SoC, Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. + Parameter in Prozent [%] für den Maximal-SoC am LP1 im Modus PV-Laden. Definiert einen EV-Maximal-SoC, bei dessen Überschreitung die Ladung gestoppt wird. From f2a6515004c1ce4a1ef1a3357393c120059effe6 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 13 Oct 2022 16:12:16 +0200 Subject: [PATCH 004/201] option for Sungrow SG with WiNet-dongle --- packages/modules/sungrow/counter.py | 29 ++++++++++++++--------------- packages/modules/sungrow/device.py | 4 +++- packages/modules/sungrow/version.py | 7 +++++++ web/settings/modulconfigevu.php | 1 + 4 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 packages/modules/sungrow/version.py diff --git a/packages/modules/sungrow/counter.py b/packages/modules/sungrow/counter.py index 79690e6bc3..725bb5a5db 100644 --- a/packages/modules/sungrow/counter.py +++ b/packages/modules/sungrow/counter.py @@ -10,6 +10,7 @@ from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.sungrow.config import SungrowCounterSetup +from modules.sungrow.version import Version class SungrowCounter: @@ -28,30 +29,28 @@ def __init__(self, def update(self): unit = self.__device_modbus_id - if self.component_config.configuration.version == 1: - power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) - frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 - voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) - voltages = [voltage / 10 for voltage in voltages] + if self.component_config.configuration.version == Version.SH: + power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) * -1 # no valid data for powers per phase - # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.UINT_16] * 3, + # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_16] * 3, # wordorder=Endian.Little, unit=unit) # powers = [power / 10 for power in powers] # log.info("power: " + str(power) + " powers?: " + str(powers)) else: - power = self.__tcp_client.read_input_registers(13009, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 - voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) - voltages = [voltage / 10 for voltage in voltages] + power = self.__tcp_client.read_input_registers(5082, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=unit) + if self.component_config.configuration.version == Version.SG_winet_dongle: + power = power * -1 # no valid data for powers per phase - # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.INT_16] * 3, + # powers = self.__tcp_client.read_input_registers(5084, [ModbusDataType.UINT_16] * 3, # wordorder=Endian.Little, unit=unit) # powers = [power / 10 for power in powers] # log.info("power: " + str(power) + " powers?: " + str(powers)) + frequency = self.__tcp_client.read_input_registers(5035, ModbusDataType.UINT_16, unit=unit) / 10 + voltages = self.__tcp_client.read_input_registers(5018, [ModbusDataType.UINT_16] * 3, + wordorder=Endian.Little, unit=unit) + voltages = [voltage / 10 for voltage in voltages] imported, exported = self.__sim_counter.sim_count(power) diff --git a/packages/modules/sungrow/device.py b/packages/modules/sungrow/device.py index 675438d8d9..4457af581f 100644 --- a/packages/modules/sungrow/device.py +++ b/packages/modules/sungrow/device.py @@ -12,6 +12,8 @@ from modules.sungrow import inverter from modules.sungrow.config import (Sungrow, SungrowBatSetup, SungrowCounterConfiguration, SungrowCounterSetup, SungrowInverterSetup) +from modules.sungrow.version import Version + log = logging.getLogger(__name__) @@ -91,7 +93,7 @@ def read_legacy_bat(ip_address: str, port: int, modbus_id: int, num: Optional[in def read_legacy_counter(ip_address: str, port: int, modbus_id: int, version: int): read_legacy(ip_address, port, modbus_id, counter.component_descriptor.configuration_factory( - id=None, configuration=SungrowCounterConfiguration(version=version))) + id=None, configuration=SungrowCounterConfiguration(version=Version(version)))) def read_legacy_inverter(ip_address: str, port: int, modbus_id: int, num: int): diff --git a/packages/modules/sungrow/version.py b/packages/modules/sungrow/version.py new file mode 100644 index 0000000000..6e3607756b --- /dev/null +++ b/packages/modules/sungrow/version.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class Version(IntEnum): + SH = 0 + SG = 1 + SG_winet_dongle = 2 diff --git a/web/settings/modulconfigevu.php b/web/settings/modulconfigevu.php index 3291cef114..f0be2ce82a 100644 --- a/web/settings/modulconfigevu.php +++ b/web/settings/modulconfigevu.php @@ -149,6 +149,7 @@ From 16eec84c9b7dca8f4a05867973955c7fe0d0d696 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 21 Oct 2022 14:40:55 +0200 Subject: [PATCH 005/201] =?UTF-8?q?Erl=C3=A4uterung=20Regelmodus=20erg?= =?UTF-8?q?=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/settings/pvconfig.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index 155f394ed4..6c58211e32 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -63,7 +63,7 @@

Einstellungen für PV- und Min+PV-Laden

manueller Regelpunkt - Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. + Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. Es gelingt ja nicht den Übergabepunkt exakt auf 0 zu regeln, mit dieser Einstellung wurd nun konfiguriert ob eher etwas Einspeisung (Netzbezug vermeiden) oder eher etwas Netzbezug (Eigenverbrauch maximieren) erfolgen soll.
From 9539b21602650dcc3a578ad8ab51a4bbc89b9526 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Sat, 22 Oct 2022 22:46:15 +0200 Subject: [PATCH 006/201] fix typo --- web/settings/pvconfig.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index 6c58211e32..fcae758192 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -63,7 +63,7 @@

Einstellungen für PV- und Min+PV-Laden

manueller Regelpunkt
- Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. Es gelingt ja nicht den Übergabepunkt exakt auf 0 zu regeln, mit dieser Einstellung wurd nun konfiguriert ob eher etwas Einspeisung (Netzbezug vermeiden) oder eher etwas Netzbezug (Eigenverbrauch maximieren) erfolgen soll. + Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. Es gelingt ja nicht den Übergabepunkt exakt auf 0 zu regeln, mit dieser Einstellung wird nun konfiguriert ob eher etwas Einspeisung (Netzbezug vermeiden) oder eher etwas Netzbezug (Eigenverbrauch maximieren) erfolgen soll.
From 9e26d68fd80c68943b5f4d65b74bb4f5185bcd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mo=C3=9Fner?= Date: Wed, 3 Nov 2021 12:22:13 +0100 Subject: [PATCH 007/201] fix/beautify log output --- modules/smarthome/http/on.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smarthome/http/on.py b/modules/smarthome/http/on.py index f263714443..a28a231078 100644 --- a/modules/smarthome/http/on.py +++ b/modules/smarthome/http/on.py @@ -22,6 +22,6 @@ f = open( file_string , 'a') else: f = open( file_string , 'w') -print ('%s devicenr %s url %s)' % (time_string,devicenumber,url),file=f) +print ('%s devicenr %s url %s' % (time_string,devicenumber,url),file=f) f.close() urllib.request.urlopen(url, timeout=5) \ No newline at end of file From 10050456ad2c82165ea02ad9678143df9ceebf48 Mon Sep 17 00:00:00 2001 From: benderl Date: Thu, 27 Oct 2022 12:06:09 +0200 Subject: [PATCH 008/201] update description "Regelmodus" --- web/settings/pvconfig.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/settings/pvconfig.html b/web/settings/pvconfig.html index fcae758192..295368f894 100644 --- a/web/settings/pvconfig.html +++ b/web/settings/pvconfig.html @@ -63,7 +63,10 @@

Einstellungen für PV- und Min+PV-Laden

manueller Regelpunkt
- Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. Es gelingt ja nicht den Übergabepunkt exakt auf 0 zu regeln, mit dieser Einstellung wird nun konfiguriert ob eher etwas Einspeisung (Netzbezug vermeiden) oder eher etwas Netzbezug (Eigenverbrauch maximieren) erfolgen soll. + + Auswahl des Regelmodus in beiden Lademodi. Die Auswahl beeinflusst den Regelpunkt und die Berechnung des Ladestroms. + Es ist technisch nicht möglich, den Übergabepunkt exakt auf 0 zu regeln. Mit dieser Einstellung wird festgelegt, ob eher etwas Einspeisung (Netzbezug vermeiden) oder Netzbezug (Eigenverbrauch maximieren) erfolgen soll. +
From f72ba73d90e3a51cd69b04c9a76828f261589c5f Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 27 Oct 2022 12:17:24 +0200 Subject: [PATCH 009/201] fix flake8 color theme --- web/themes/colors/settings.css | 2 +- web/themes/colors/theme.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/themes/colors/settings.css b/web/themes/colors/settings.css index 9b05803704..87637b9a85 100644 --- a/web/themes/colors/settings.css +++ b/web/themes/colors/settings.css @@ -84,7 +84,7 @@ pre { body { background-color: var(--color-bg); - color: var(--color-fg); + color: var(--color-fg); } input, select { diff --git a/web/themes/colors/theme.html b/web/themes/colors/theme.html index 05ab7e55f2..66628f2bdd 100644 --- a/web/themes/colors/theme.html +++ b/web/themes/colors/theme.html @@ -1149,4 +1149,4 @@ - \ No newline at end of file + From 50bb28bc534c495fd573d88912f85458b8f32d68 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 27 Oct 2022 14:48:23 +0200 Subject: [PATCH 010/201] JSON: fix missing queries --- packages/modules/json/bat.py | 2 +- packages/modules/json/config.py | 11 ++++++++--- packages/modules/json/counter.py | 2 +- packages/modules/json/device.py | 6 ++++-- packages/modules/json/inverter.py | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/modules/json/bat.py b/packages/modules/json/bat.py index c482f2a47c..3db2b9489b 100644 --- a/packages/modules/json/bat.py +++ b/packages/modules/json/bat.py @@ -29,7 +29,7 @@ def update(self, response) -> None: else: soc = 0 - if config.jq_imported != "" and config.jq_exported != "": + if config.jq_imported is not None and config.jq_exported is not None: imported = jq.compile(config.jq_imported).input(response).first() exported = jq.compile(config.jq_exported).input(response).first() else: diff --git a/packages/modules/json/config.py b/packages/modules/json/config.py index 2905452687..645a58ec18 100644 --- a/packages/modules/json/config.py +++ b/packages/modules/json/config.py @@ -1,3 +1,4 @@ +from typing import Optional from modules.common.component_setup import ComponentSetup @@ -19,7 +20,11 @@ def __init__(self, class JsonBatConfiguration: - def __init__(self, jq_imported=None, jq_exported=None, jq_soc=None, jq_power=None,): + def __init__(self, + jq_imported: Optional[str] = None, + jq_exported: Optional[str] = None, + jq_soc: str = "", + jq_power: str = ""): self.jq_imported = jq_imported self.jq_exported = jq_exported self.jq_soc = jq_soc @@ -36,7 +41,7 @@ def __init__(self, class JsonCounterConfiguration: - def __init__(self, jq_power=None, jq_exported=None, jq_imported=None,): + def __init__(self, jq_power: str = "", jq_exported: Optional[str] = None, jq_imported: Optional[str] = None): self.jq_power = jq_power self.jq_exported = jq_exported self.jq_imported = jq_imported @@ -52,7 +57,7 @@ def __init__(self, class JsonInverterConfiguration: - def __init__(self, jq_power=None, jq_exported=None): + def __init__(self, jq_power: str = "", jq_exported: Optional[str] = None): self.jq_power = jq_power self.jq_exported = jq_exported diff --git a/packages/modules/json/counter.py b/packages/modules/json/counter.py index f0e9e3ef06..ff50844526 100644 --- a/packages/modules/json/counter.py +++ b/packages/modules/json/counter.py @@ -25,7 +25,7 @@ def update(self, response): power = jq.compile(config.jq_power).input(response).first() # ToDo: add current or power per phase - if config.jq_imported == "" or config.jq_exported == "": + if config.jq_exported is None or config.jq_exported is None: imported, exported = self.__sim_counter.sim_count(power) else: imported = jq.compile(config.jq_imported).input(response).first() diff --git a/packages/modules/json/device.py b/packages/modules/json/device.py index a7c11adb72..ca26a41fed 100644 --- a/packages/modules/json/device.py +++ b/packages/modules/json/device.py @@ -86,14 +86,16 @@ def read_legacy_bat(ip_address: str, jq_power: str, jq_soc: str): def read_legacy_counter(ip_address: str, jq_power: str, jq_imported: str, jq_exported: str): - config = JsonCounterConfiguration(jq_power=jq_power, jq_imported=jq_imported, jq_exported=jq_exported) + config = JsonCounterConfiguration(jq_power=jq_power, + jq_imported=None if jq_imported == "" else jq_imported, + jq_exported=None if jq_exported == "" else jq_exported) read_legacy( ip_address, counter.component_descriptor.configuration_factory(id=None, configuration=config)) def read_legacy_inverter(ip_address: str, jq_power: str, jq_exported: str, num: int): - config = JsonInverterConfiguration(jq_power=jq_power, jq_exported=jq_exported) + config = JsonInverterConfiguration(jq_power=jq_power, jq_exported=None if jq_exported == "" else jq_exported) read_legacy(ip_address, inverter.component_descriptor.configuration_factory(id=num, configuration=config)) diff --git a/packages/modules/json/inverter.py b/packages/modules/json/inverter.py index 853405c392..da71d20bec 100644 --- a/packages/modules/json/inverter.py +++ b/packages/modules/json/inverter.py @@ -26,7 +26,7 @@ def update(self, response) -> None: power = float(jq.compile(config.jq_power).input(response).first()) if power >= 0: power = power * -1 - if config.jq_exported == "": + if config.jq_exported is None: _, exported = self.__sim_counter.sim_count(power) else: exported = jq.compile(config.jq_exported).input(response).first() From 2351c4ba311cf9949a39e0d8fe238fed1ba3d147 Mon Sep 17 00:00:00 2001 From: Yannik Hampe Date: Tue, 18 Oct 2022 21:49:09 +0200 Subject: [PATCH 011/201] use ConfigurableDevice for json module to shorten and simplify code --- .github/workflows/github-actions-python.yml | 2 +- .../modules/common/configurable_device.py | 8 +- packages/modules/conftest.py | 1 - packages/modules/json/device.py | 85 ++++++------------- packages/modules/json/device_test.py | 42 +++++++++ 5 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 packages/modules/json/device_test.py diff --git a/.github/workflows/github-actions-python.yml b/.github/workflows/github-actions-python.yml index a13fa9b689..c23407ca82 100644 --- a/.github/workflows/github-actions-python.yml +++ b/.github/workflows/github-actions-python.yml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest paho-mqtt requests-mock + pip install flake8 pytest paho-mqtt requests-mock jq - name: Flake8 with annotations uses: TrueBrain/actions-flake8@v2.1 with: diff --git a/packages/modules/common/configurable_device.py b/packages/modules/common/configurable_device.py index 1e75acb751..5689c7625c 100644 --- a/packages/modules/common/configurable_device.py +++ b/packages/modules/common/configurable_device.py @@ -48,7 +48,13 @@ def __call__(self, component_config: T_COMPONENT_CONFIG) -> T_COMPONENT: raise Exception( "Unknown component type <%s>, known types are: <%s>", e, ','.join(self.__type_to_factory.keys()) ) - required_type, = inspect.getfullargspec(factory).annotations.values() + arg_spec = inspect.getfullargspec(factory) + if len(arg_spec.args) != 1: + raise Exception( + "Expected function with single argument, however factory for %s has args: %s" % + (component_type, arg_spec.args) + ) + required_type = arg_spec.annotations[arg_spec.args[0]] return factory(dataclass_from_dict(required_type, component_config)) diff --git a/packages/modules/conftest.py b/packages/modules/conftest.py index e400df9ed5..865b93fc4b 100644 --- a/packages/modules/conftest.py +++ b/packages/modules/conftest.py @@ -5,7 +5,6 @@ from modules.common import simcount -sys.modules['jq'] = type(sys)('jq') sys.modules['pymodbus'] = type(sys)('pymodbus') module = type(sys)('pymodbus.client.sync') diff --git a/packages/modules/json/device.py b/packages/modules/json/device.py index ca26a41fed..0920939d9c 100644 --- a/packages/modules/json/device.py +++ b/packages/modules/json/device.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 import logging -from typing import Dict, List, Union +from typing import List, Union, Iterable -from dataclass_utils import dataclass_from_dict from helpermodules.cli import run_using_positional_cli_args from modules.common import req -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.json import bat, counter, inverter +from modules.json.bat import JsonBat from modules.json.config import (Json, JsonBatConfiguration, JsonBatSetup, @@ -16,66 +16,37 @@ JsonCounterSetup, JsonInverterConfiguration, JsonInverterSetup) +from modules.json.counter import JsonCounter +from modules.json.inverter import JsonInverter log = logging.getLogger(__name__) +JsonComponent = Union[JsonBat, JsonCounter, JsonInverter] -json_component_classes = Union[bat.JsonBat, counter.JsonCounter, inverter.JsonInverter] - - -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.JsonBat, - "counter": counter.JsonCounter, - "inverter": inverter.JsonInverter - } - - def __init__(self, device_config: Union[Dict, Json]) -> None: - self.components = {} # type: Dict[str, json_component_classes] - try: - self.device_config = dataclass_from_dict(Json, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, JsonBatSetup, JsonCounterSetup, JsonInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - with MultiComponentUpdateContext(self.components): - response = req.get_http_session().get(self.device_config.configuration.url, timeout=5) - for component in self.components: - self.components[component].update(response.json()) - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} +def create_device(device_config: Json): + def create_bat(component_config: JsonBatSetup) -> JsonBat: + return JsonBat(device_config.id, component_config) + + def create_counter(component_config: JsonCounterSetup) -> JsonCounter: + return JsonCounter(device_config.id, component_config) + + def create_inverter(component_config: JsonInverterSetup) -> JsonInverter: + return JsonInverter(device_config.id, component_config) + + def update_components(components: Iterable[JsonComponent]): + response = req.get_http_session().get(device_config.configuration.url, timeout=5).json() + for component in components: + component.update(response) + + return ConfigurableDevice( + device_config, + component_factory=ComponentFactoryByType(bat=create_bat, counter=create_counter, inverter=create_inverter), + component_updater=MultiComponentUpdater(update_components) + ) def read_legacy(url: str, component_config: Union[JsonBatSetup, JsonCounterSetup, JsonInverterSetup]) -> None: - dev = Device(Json(configuration=JsonConfiguration(url=url))) + dev = create_device(Json(configuration=JsonConfiguration(url=url))) dev.add_component(component_config) dev.update() diff --git a/packages/modules/json/device_test.py b/packages/modules/json/device_test.py new file mode 100644 index 0000000000..c630ee7b72 --- /dev/null +++ b/packages/modules/json/device_test.py @@ -0,0 +1,42 @@ +from unittest.mock import Mock + +import pytest +import requests_mock + +from modules.common.fault_state import FaultState +from modules.json import bat, counter, inverter +from modules.json.config import Json, JsonConfiguration, JsonBatSetup, JsonBatConfiguration, \ + JsonInverterConfiguration, JsonInverterSetup, JsonCounterSetup, JsonCounterConfiguration +from modules.json.device import create_device + + +@pytest.fixture +def mock_value_store(monkeypatch): + mock_value_store = Mock() + mock_value_store_factory = Mock(return_value=mock_value_store) + monkeypatch.setattr(bat, "get_bat_value_store", mock_value_store_factory) + monkeypatch.setattr(counter, "get_counter_value_store", mock_value_store_factory) + monkeypatch.setattr(inverter, "get_inverter_value_store", mock_value_store_factory) + return mock_value_store + + +@pytest.mark.parametrize("component_config,expected_power", [ + pytest.param(JsonBatSetup(configuration=JsonBatConfiguration(jq_power=".some_value")), 42.0, id="bat"), + pytest.param(JsonCounterSetup(configuration=JsonCounterConfiguration(jq_power=".some_value")), 42.0, id="counter"), + pytest.param(JsonInverterSetup(configuration=JsonInverterConfiguration(jq_power=".some_value")), -42.0, id="pv"), +]) +def test_device(monkeypatch, mock_value_store: Mock, requests_mock: requests_mock.Mocker, component_config, + expected_power: float): + # setup + monkeypatch.setattr(FaultState, "store_error", Mock()) + requests_mock.get("http://sample_host/sample_path", json={"some_value": 42}) + device_config = Json(configuration=JsonConfiguration("http://sample_host/sample_path")) + + # execution + device = create_device(device_config) + device.add_component(component_config) + device.update() + + # evaluation + assert len(mock_value_store.set.mock_calls) == 1 + assert mock_value_store.set.call_args[0][0].power == expected_power From 390b49d89189d351aa9b69a293c2f34647965d0c Mon Sep 17 00:00:00 2001 From: okaegi Date: Thu, 27 Oct 2022 18:11:49 +0200 Subject: [PATCH 012/201] Ratiotherm WP und ndeuen Dac type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Erster openwb driver für die Wärmepumpe ratiotherm. Der Anschluss wird über modbus RTU mit ELFIN-EE11 auf can-ze3 gemacht. Dac driver mit typ ergänzt --- modules/smarthome/nxdacxx/watt.py | 15 ++- modules/smarthome/ratiotherm/off.py | 40 +++++++ modules/smarthome/ratiotherm/on.py | 33 ++++++ modules/smarthome/ratiotherm/watt.py | 107 ++++++++++++++++++ runs/mqttsub.py | 8 +- runs/smarthomemq.py | 3 + runs/usmarthome/smartnxdacxx.py | 10 +- runs/usmarthome/smartratiotherm.py | 51 +++++++++ web/settings/smarthomeconfig.php | 28 ++++- .../topicsToSubscribe_smarthomeconfig.js | 1 + 10 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 modules/smarthome/ratiotherm/off.py create mode 100644 modules/smarthome/ratiotherm/on.py create mode 100644 modules/smarthome/ratiotherm/watt.py create mode 100644 runs/usmarthome/smartratiotherm.py diff --git a/modules/smarthome/nxdacxx/watt.py b/modules/smarthome/nxdacxx/watt.py index 75c7aeef27..9231eb2656 100644 --- a/modules/smarthome/nxdacxx/watt.py +++ b/modules/smarthome/nxdacxx/watt.py @@ -12,6 +12,7 @@ maxpower = int(sys.argv[4]) forcesend = int(sys.argv[5]) port = int(sys.argv[6]) +dactyp = int(sys.argv[7]) # forcesend = 0 default acthor time period applies # forcesend = 1 default overwritten send now # forcesend = 9 default overwritten no send @@ -83,12 +84,18 @@ # modbus write if modbuswrite == 1: client = ModbusTcpClient(ipadr, port=port) - volt = int((neupower * 1000) / maxpower) - rq = client.write_register(1, volt, unit=1) + if dactyp == 0: + # 10 Volts are 1000 + volt = int((neupower * 1000) / maxpower) + rq = client.write_register(1, volt, unit=1) + else: + # 10 volts are 4000 + volt = int((neupower * 4000) / maxpower) + rq = client.write_register(0x01f4, volt, unit=1) if count1 < 3: with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s Volt %6d written by modbus ' % - (time_string, devicenumber, ipadr, volt), file=f) + print('%s devicenr %s ipadr %s Volt %6d dactyp %d written by modbus ' % + (time_string, devicenumber, ipadr, volt, dactyp), file=f) with open(file_stringcount, 'w') as f: f.write(str(count1)) else: diff --git a/modules/smarthome/ratiotherm/off.py b/modules/smarthome/ratiotherm/off.py new file mode 100644 index 0000000000..98daf177b1 --- /dev/null +++ b/modules/smarthome/ratiotherm/off.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +import sys +import os +import time + +named_tuple = time.localtime() # getstruct_time +time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm off.py", named_tuple) +devicenumber = str(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +bp = '/var/www/html/openWB/ramdisk/smarthome_device_' +# standard +file_string = bp + str(devicenumber) + '_ratiotherm.log' +file_stringpv = bp + str(devicenumber) + '_pv' +file_stringcount = bp + str(devicenumber) + '_count' +if os.path.isfile(file_string): + pass +else: + with open(file_string, 'w') as f: + print('ratiotherm start log', file=f) +with open(file_string, 'a') as f: + print('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' + % (time_string, devicenumber, ipadr, uberschuss), file=f) +aktpower = 0 +with open(file_string, 'a') as f: + print('%s devicenr %s ipadr %s Akt Leistung %6d' + % (time_string, devicenumber, ipadr, aktpower), file=f) +pvmodus = 0 +if os.path.isfile(file_stringpv): + with open(file_stringpv, 'r') as f: + pvmodus = int(f.read()) +# wenn vorher pvmodus an, dann watt.py +# signaliseren einmalig 0 ueberschuss zu schicken +if pvmodus == 1: + pvmodus = 99 +with open(file_stringpv, 'w') as f: + f.write(str(pvmodus)) +count1 = 999 +with open(file_stringcount, 'w') as f: + f.write(str(count1)) diff --git a/modules/smarthome/ratiotherm/on.py b/modules/smarthome/ratiotherm/on.py new file mode 100644 index 0000000000..3284482859 --- /dev/null +++ b/modules/smarthome/ratiotherm/on.py @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +import sys +import os +import time +named_tuple = time.localtime() # getstruct_time +time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm on.py", named_tuple) +devicenumber = str(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +bp = '/var/www/html/openWB/ramdisk/smarthome_device_' +# standard +# lesen +# own log +file_string = bp + str(devicenumber) + '_ratiotherm.log' +file_stringpv = bp + str(devicenumber) + '_pv' +file_stringcount = bp + str(devicenumber) + '_count' +if os.path.isfile(file_string): + pass +else: + with open(file_string, 'w') as f: + print('ratiotherm start log', file=f) +with open(file_string, 'a') as f: + print('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' + % (time_string, devicenumber, ipadr, uberschuss), file=f) +aktpower = 0 +with open(file_string, 'a') as f: + print('%s devicenr %s ipadr %s Akt Leistung %6d ' % + (time_string, devicenumber, ipadr, aktpower), file=f) +with open(file_stringpv, 'w') as f: + f.write(str(1)) +count1 = 999 +with open(file_stringcount, 'w') as f: + f.write(str(count1)) diff --git a/modules/smarthome/ratiotherm/watt.py b/modules/smarthome/ratiotherm/watt.py new file mode 100644 index 0000000000..276f1776e8 --- /dev/null +++ b/modules/smarthome/ratiotherm/watt.py @@ -0,0 +1,107 @@ +#!/usr/bin/python3 +import sys +import os +import time +import json +from pymodbus.payload import BinaryPayloadBuilder, Endian +from pymodbus.client.sync import ModbusTcpClient +named_tuple = time.localtime() # getstruct_time +time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm watty.py", named_tuple) +devicenumber = str(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +forcesend = int(sys.argv[4]) +# forcesend = 0 default acthor time period applies +# forcesend = 1 default overwritten send now +# forcesend = 9 default overwritten no send +bp = '/var/www/html/openWB/ramdisk/smarthome_device_' +file_string = bp + str(devicenumber) + '_ratiotherm.log' +file_stringpv = bp + str(devicenumber) + '_pv' +file_stringcount = bp + str(devicenumber) + '_count' +file_stringcount5 = bp + str(devicenumber) + '_count5' +count5 = 999 +modbuswrite = 0 +neupower = 0 +if os.path.isfile(file_stringcount5): + with open(file_stringcount5, 'r') as f: + count5 = int(f.read()) +if (forcesend == 0): + count5 = count5 + 1 +elif (forcesend == 1): + count5 = 999 +else: + count5 = 1 +if count5 > 3: + count5 = 0 +with open(file_stringcount5, 'w') as f: + f.write(str(count5)) +# pv modus +pvmodus = 0 +if os.path.isfile(file_stringpv): + with open(file_stringpv, 'r') as f: + pvmodus = int(f.read()) +aktpower = 0 +if count5 == 0: + # log counter + count1 = 999 + if os.path.isfile(file_stringcount): + with open(file_stringcount, 'r') as f: + count1 = int(f.read()) + count1 = count1+1 + if count1 > 80: + count1 = 0 + with open(file_stringcount, 'w') as f: + f.write(str(count1)) + # logik nur schicken bei pvmodus + if pvmodus == 1: + modbuswrite = 1 + neupower = uberschuss + if neupower < 0: + neupower = 0 + if neupower > 32767: + neupower = 32767 + # wurde ratiotherm gerade ausgeschaltet ? (pvmodus == 99 ?) + # dann 0 schicken wenn kein pvmodus mehr + # und pv modus ausschalten + if pvmodus == 99: + modbuswrite = 1 + neupower = 0 + pvmodus = 0 + with open(file_stringpv, 'w') as f: + f.write(str(pvmodus)) + if count1 < 3: + if os.path.isfile(file_string): + pass + else: + with open(file_string, 'w') as f: + print('ratiotherm start log', file=f) + with open(file_string, 'a') as f: + print('%s Nr %s ipadr %s ueberschuss %6d Akt Leistung %6d' + % (time_string, devicenumber, ipadr, uberschuss, aktpower), + file=f) + print('%s Nr %s ipadr %s neupower %6d pvmodus %1d modbusw %1d' + % (time_string, devicenumber, ipadr, neupower, pvmodus, + modbuswrite), file=f) + # modbus write + if modbuswrite == 1: + # andernfalls absturz bei negativen Zahlen + builder = BinaryPayloadBuilder(byteorder=Endian.Big) + builder.reset() + builder.add_16bit_int(neupower) + pay = builder.to_registers() + client = ModbusTcpClient(ipadr, port=502) + client.write_register(100, pay[0], unit=1) + if count1 < 3: + with open(file_string, 'a') as f: + print('%s devicenr %s ipadr %s written %6d %#4X' % + (time_string, devicenumber, ipadr, pay[0], pay[0]), + file=f) +else: + if pvmodus == 99: + pvmodus = 0 +answer = '{"power":' + str(aktpower) + ',"powerc":0' +answer += ',"send":' + str(modbuswrite) + ',"sendpower":' + str(neupower) +answer += ',"on":' + str(pvmodus) + '}' +with open('/var/www/html/openWB/ramdisk/smarthome_device_ret' + + str(devicenumber), 'w') as f1: + json.dump(answer, f1) diff --git a/runs/mqttsub.py b/runs/mqttsub.py index 4cafcfad81..4135eb2f22 100755 --- a/runs/mqttsub.py +++ b/runs/mqttsub.py @@ -140,6 +140,11 @@ def on_message(client, userdata, msg): if ( 1 <= int(devicenumb) <= numberOfSupportedDevices and 0 <= int(msg.payload) <= 6): writetoconfig(shconfigfile,'smarthomedevices','device_chan_'+str(devicenumb),msg.payload.decode("utf-8")) client.publish("openWB/config/get/SmartHome/Devices/"+str(devicenumb)+"/device_chan", msg.payload.decode("utf-8"), qos=0, retain=True) + if (( "openWB/config/set/SmartHome/Device" in msg.topic) and ("device_nxdacxxtype" in msg.topic)): + devicenumb=re.sub(r'\D', '', msg.topic) + if ( 1 <= int(devicenumb) <= numberOfSupportedDevices and 0 <= int(msg.payload) <= 2): + writetoconfig(shconfigfile,'smarthomedevices','device_nxdacxxtype_'+str(devicenumb),msg.payload.decode("utf-8")) + client.publish("openWB/config/get/SmartHome/Devices/"+str(devicenumb)+"/device_nxdacxxtype", msg.payload.decode("utf-8"), qos=0, retain=True) if (( "openWB/config/set/SmartHome/Device" in msg.topic) and ("device_measchan" in msg.topic)): devicenumb=re.sub(r'\D', '', msg.topic) if ( 1 <= int(devicenumb) <= numberOfSupportedDevices and 0 <= int(msg.payload) <= 6): @@ -179,7 +184,8 @@ def on_message(client, userdata, msg): client.publish("openWB/config/get/SmartHome/Devices/"+str(devicenumb)+"/device_name", msg.payload.decode("utf-8"), qos=0, retain=True) if (( "openWB/config/set/SmartHome/Device" in msg.topic) and ("device_type" in msg.topic)): devicenumb=re.sub(r'\D', '', msg.topic) - validDeviceTypes = ['none','shelly','tasmota','acthor','lambda','elwa','idm','vampair','stiebel','http','avm','mystrom','viessmann','mqtt','NXDACXX','pyt'] # 'pyt' is deprecated and will be removed! + validDeviceTypes = ['none','shelly','tasmota','acthor','lambda','elwa','idm','vampair','stiebel','http','avm','mystrom','viessmann','mqtt','NXDACXX', + 'ratiotherm','pyt'] # 'pyt' is deprecated and will be removed! if ( 1 <= int(devicenumb) <= numberOfSupportedDevices and len(str(msg.payload.decode("utf-8"))) > 2): try: # just check vor payload in list, deviceTypeIndex is not used diff --git a/runs/smarthomemq.py b/runs/smarthomemq.py index 9e20f9e757..af4cd6400b 100755 --- a/runs/smarthomemq.py +++ b/runs/smarthomemq.py @@ -21,6 +21,7 @@ from usmarthome.smartlambda import Slambda from usmarthome.smarttasmota import Stasmota from usmarthome.smartviessmann import Sviessmann +from usmarthome.smartratiotherm import Sratiotherm mqtt_cache = {} mydevices = [] @@ -343,6 +344,8 @@ def update_devices(): mydevice = Svampair() elif (device_type == 'lambda'): mydevice = Slambda() + elif (device_type == 'ratiotherm'): + mydevice = Sratiotherm() elif (device_type == 'tasmota'): mydevice = Stasmota() elif (device_type == 'avm'): diff --git a/runs/usmarthome/smartnxdacxx.py b/runs/usmarthome/smartnxdacxx.py index a0a0d654cd..6025a7bce6 100644 --- a/runs/usmarthome/smartnxdacxx.py +++ b/runs/usmarthome/smartnxdacxx.py @@ -11,6 +11,7 @@ def __init__(self): print('__init__ Snxdacxx executed') self._smart_paramadd = {} self._device_nxdacxxueb = 0 + self._device_nxdacxxtype = 0 self.device_nummer = 0 self._dynregel = 1 @@ -28,6 +29,8 @@ def updatepar(self, input_param): pass elif (key == 'device_nxdacxxueb'): self._device_nxdacxxueb = valueint + elif (key == 'device_nxdacxxtype'): + self._device_nxdacxxtype = valueint else: log.warning("(" + str(self.device_nummer) + ") " + __class__.__name__ + " überlesen " + key + @@ -40,7 +43,8 @@ def getwatt(self, uberschuss, uberschussoffset): str(self.device_nummer), str(self._device_ip), str(self.devuberschuss), str(self._device_nxdacxxueb), str(forcesend), - str(self._device_dacport)] + str(self._device_dacport), + str(self._device_nxdacxxtype)] try: self.proc = subprocess.Popen(argumentList) self.proc.communicate() @@ -52,7 +56,7 @@ def getwatt(self, uberschuss, uberschussoffset): except Exception as e1: log.warning("(" + str(self.device_nummer) + ") Leistungsmessung %s %d %s Fehlermeldung: %s " - % ('n4dac02 ', self.device_nummer, + % (' Dac ', self.device_nummer, str(self._device_ip), str(e1))) self.postwatt() @@ -71,5 +75,5 @@ def turndevicerelais(self, zustand, ueberschussberechnung, updatecnt): except Exception as e1: log.warning("(" + str(self.device_nummer) + ") on / off %s %d %s Fehlermeldung: %s " - % ('n4dac02 ', self.device_nummer, + % ('Dac ', self.device_nummer, str(self._device_ip), str(e1))) diff --git a/runs/usmarthome/smartratiotherm.py b/runs/usmarthome/smartratiotherm.py new file mode 100644 index 0000000000..b424dc4baf --- /dev/null +++ b/runs/usmarthome/smartratiotherm.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +from usmarthome.smartbase import Sbase +from usmarthome.global0 import log +import subprocess + + +class Sratiotherm(Sbase): + def __init__(self): + # setting + super().__init__() + print('__init__ Sratiotherm executed') + + def getwatt(self, uberschuss, uberschussoffset): + self.prewatt(uberschuss, uberschussoffset) + forcesend = self.checkbefsend() + argumentList = ['python3', self._prefixpy + 'ratiotherm/watt.py', + str(self.device_nummer), str(self._device_ip), + str(self.devuberschuss), + str(forcesend)] + try: + self.proc = subprocess.Popen(argumentList) + self.proc.communicate() + self.answer = self.readret() + self.newwatt = int(self.answer['power']) + self.newwattk = int(self.answer['powerc']) + self.relais = int(self.answer['on']) + self.checksend(self.answer) + except Exception as e1: + log.warning("(" + str(self.device_nummer) + + ") Leistungsmessung %s %d %s Fehlermeldung: %s " + % ('ratiotherm', self.device_nummer, + str(self._device_ip), str(e1))) + self.postwatt() + + def turndevicerelais(self, zustand, ueberschussberechnung, updatecnt): + self.preturn(zustand, ueberschussberechnung, updatecnt) + if (zustand == 1): + pname = "/on.py" + else: + pname = "/off.py" + argumentList = ['python3', self._prefixpy + 'ratiotherm' + pname, + str(self.device_nummer), str(self._device_ip), + str(self.devuberschuss)] + try: + self.proc = subprocess.Popen(argumentList) + self.proc.communicate() + except Exception as e1: + log.warning("(" + str(self.device_nummer) + + ") on / off %s %d %s Fehlermeldung: %s " + % ('ratiotherm', self.device_nummer, + str(self._device_ip), str(e1))) diff --git a/web/settings/smarthomeconfig.php b/web/settings/smarthomeconfig.php index 2a4b4f9fd0..4cb4ddb551 100644 --- a/web/settings/smarthomeconfig.php +++ b/web/settings/smarthomeconfig.php @@ -85,8 +85,9 @@ - + + @@ -131,10 +132,15 @@ openWB/SmartHome/set/Devices/4/Ueberschuss = in Watt
- N4DAC02 angesteuert über Lan. Der anliegende Überschuss wird in eine Voltzahl zwischen 0.01V und 10.0V umgewandelt. Bezug wird als 0 Volt übertragen. + DAC angesteuert über Lan. Der anliegende Überschuss wird in eine Voltzahl zwischen 0.01V und 10.0V umgewandelt. Bezug wird als 0 Volt übertragen. Wenn die Einschaltbedingung erreicht ist wird alle 30 Sekunden der gerechnete Überschuss übertragen.
+ Der konkrete Dac Typ wird im Type definiert
Mit dem Parameter Updategerät kann eine abweichende Sekundenzahl angegeben werden.
+ + Ratiothem Wärmepumpe. Anschluss via Modbus RTU (Elfin-EE11) auf CAN-EZ3. + Das Feature befindet sich noch in der Entwicklung! + Vitalcal 200-s Wärmepumpe mit LON Kommunikationsmodul und Vitogate 300. Wenn die Einschaltbedingung erreicht ist wird Komfortfunktion "Einmalige Warmwasserbereitung" außerhalb des Zeitprogramms gestartet. Für die "Einmalige Warmwasserbereitung" wird der Warmwassertemperatur-Sollwert 2 genutzt. In der Wp kann eingestellt werden, ob für diese Funktion die Elektroheizung (Heizstab) benutzt werden soll. @@ -154,7 +160,7 @@ Die Ausschaltschwelle/ Ausschaltverzögerung in OpenWB ist sinnvoll zu wählen (z.B. 500 / 3) um die Regelung von IDM nicht zu stören. - Wärmepumpe der Firma Stiebel mit ISG Plus (Servicewelt über Modbus) und SG Ready Eingang.
+ Wärmepumpe der Firma Stiebel mit ISG (Servicewelt über Modbus) und SG Ready Eingang.
Im ISG web muss unter "Einstellungen / Energiemanagement" der Parameter "SGREADY = Ein" gesetzt werden. Wenn die Einschaltbedingung erreicht ist wird der Sg Ready Eingang von Betriebszustand 2 auf Betriebszustand 3 geschaltet. Wenn die Ausbedingung erreicht ist wird der Sg Ready Eingang von Betriebszustand 3 auf Betriebszustand 2 geschaltet. @@ -224,6 +230,18 @@ Maximaler Überschuss in Watt = v10.0. Es wird kein grösserer Wert übertragen.
+
+ +
+ + + Hier ist das installierte Modell auszuwählen. + +
+
@@ -234,7 +252,7 @@
-
+

@@ -349,7 +367,7 @@
-
+

diff --git a/web/settings/topicsToSubscribe_smarthomeconfig.js b/web/settings/topicsToSubscribe_smarthomeconfig.js index 971a0ab613..8dbded9fd7 100644 --- a/web/settings/topicsToSubscribe_smarthomeconfig.js +++ b/web/settings/topicsToSubscribe_smarthomeconfig.js @@ -74,6 +74,7 @@ var topicsToSubscribe = [ ["openWB/config/get/SmartHome/Devices/+/device_chan", 0], ["openWB/config/get/SmartHome/Devices/+/device_setauto", 0], ["openWB/config/get/SmartHome/Devices/+/device_lambdaueb", 0], + ["openWB/config/get/SmartHome/Devices/+/device_nxdacxxtype", 0], ["openWB/config/get/SmartHome/Devices/+/device_nxdacxxueb", 0], ["openWB/config/get/SmartHome/Devices/+/device_measuresmaage", 0] From 4f27a79725f82e1399976a2b9c09edbaf13abdcf Mon Sep 17 00:00:00 2001 From: okaegi Date: Thu, 27 Oct 2022 18:17:53 +0200 Subject: [PATCH 013/201] update spaces --- web/settings/smarthomeconfig.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/settings/smarthomeconfig.php b/web/settings/smarthomeconfig.php index 4cb4ddb551..6f8facdff9 100644 --- a/web/settings/smarthomeconfig.php +++ b/web/settings/smarthomeconfig.php @@ -245,10 +245,10 @@
- - - Standardeinstellungen:8899
-
+ + + Standardeinstellungen:8899
+
From 18e86267b75d3638260dc3658e6548a6674525ac Mon Sep 17 00:00:00 2001 From: okaegi Date: Thu, 27 Oct 2022 18:23:07 +0200 Subject: [PATCH 014/201] updated spaces..(2) --- web/settings/smarthomeconfig.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/settings/smarthomeconfig.php b/web/settings/smarthomeconfig.php index 6f8facdff9..8b348d3458 100644 --- a/web/settings/smarthomeconfig.php +++ b/web/settings/smarthomeconfig.php @@ -535,7 +535,7 @@ -
@@ -577,7 +577,7 @@ -
-
+
jede volle Stunde / jede halbe Stunde prüfen oder ausschalten -
From 40c09d349af8977d2fbc555f43250251ffd72e17 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 31 Oct 2022 09:17:20 +0100 Subject: [PATCH 016/201] fix pv power if dc power is 0 --- packages/modules/solaredge/device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/modules/solaredge/device.py b/packages/modules/solaredge/device.py index 9219f819cc..59a24537d6 100644 --- a/packages/modules/solaredge/device.py +++ b/packages/modules/solaredge/device.py @@ -219,7 +219,10 @@ def create_inverter(modbus_id: int) -> SolaredgeInverter: with dev.client: for inv in inverters: state = inv.read_state() - total_power += state.power + if state.dc_power == 0: + total_power += 0 + else: + total_power += state.power total_energy += state.exported total_currents = list(map(add, total_currents, state.currents)) From 42141a6ae855488157330394cf2f1d2b853ad03b Mon Sep 17 00:00:00 2001 From: Yannik Hampe Date: Mon, 31 Oct 2022 11:18:30 +0100 Subject: [PATCH 017/201] json module: make sure values retrieved are float. Be fail-fast if not. --- packages/modules/json/bat.py | 8 ++++---- packages/modules/json/counter.py | 6 +++--- packages/modules/json/inverter.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/modules/json/bat.py b/packages/modules/json/bat.py index 3db2b9489b..e1ab2c95e4 100644 --- a/packages/modules/json/bat.py +++ b/packages/modules/json/bat.py @@ -23,15 +23,15 @@ def __init__(self, device_id: int, component_config: Union[Dict, JsonBatSetup]) def update(self, response) -> None: config = self.component_config.configuration - power = jq.compile(config.jq_power).input(response).first() + power = float(jq.compile(config.jq_power).input(response).first()) if config.jq_soc != "": - soc = jq.compile(config.jq_soc).input(response).first() + soc = float(jq.compile(config.jq_soc).input(response).first()) else: soc = 0 if config.jq_imported is not None and config.jq_exported is not None: - imported = jq.compile(config.jq_imported).input(response).first() - exported = jq.compile(config.jq_exported).input(response).first() + imported = float(jq.compile(config.jq_imported).input(response).first()) + exported = float(jq.compile(config.jq_exported).input(response).first()) else: imported, exported = self.__sim_counter.sim_count(power) diff --git a/packages/modules/json/counter.py b/packages/modules/json/counter.py index ff50844526..93345362de 100644 --- a/packages/modules/json/counter.py +++ b/packages/modules/json/counter.py @@ -23,13 +23,13 @@ def __init__(self, device_id: int, component_config: Union[Dict, JsonCounterSetu def update(self, response): config = self.component_config.configuration - power = jq.compile(config.jq_power).input(response).first() + power = float(jq.compile(config.jq_power).input(response).first()) # ToDo: add current or power per phase if config.jq_exported is None or config.jq_exported is None: imported, exported = self.__sim_counter.sim_count(power) else: - imported = jq.compile(config.jq_imported).input(response).first() - exported = jq.compile(config.jq_exported).input(response).first() + imported = float(jq.compile(config.jq_imported).input(response).first()) + exported = float(jq.compile(config.jq_exported).input(response).first()) counter_state = CounterState( imported=imported, diff --git a/packages/modules/json/inverter.py b/packages/modules/json/inverter.py index da71d20bec..ab3c66c380 100644 --- a/packages/modules/json/inverter.py +++ b/packages/modules/json/inverter.py @@ -29,7 +29,7 @@ def update(self, response) -> None: if config.jq_exported is None: _, exported = self.__sim_counter.sim_count(power) else: - exported = jq.compile(config.jq_exported).input(response).first() + exported = float(jq.compile(config.jq_exported).input(response).first()) inverter_state = InverterState( power=power, From 9d0d82e5801effc2dcbeb2ba602f551ef348abbf Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 31 Oct 2022 12:06:03 +0100 Subject: [PATCH 018/201] fix: cp num changes after daemon start --- runs/isss.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runs/isss.py b/runs/isss.py index 58458ab48e..0d2b178577 100755 --- a/runs/isss.py +++ b/runs/isss.py @@ -49,11 +49,11 @@ class UpdateValues: def __init__(self, local_charge_point_num: int) -> None: self.local_charge_point_num_str = str(local_charge_point_num) - self.cp_num_str = str(Isss.get_cp_num(local_charge_point_num)) self.old_counter_state = None def update_values(self, counter_state: ChargepointState) -> None: self.parent_wb = Isss.get_parent_wb() + self.cp_num_str = str(Isss.get_cp_num(int(self.local_charge_point_num_str))) if self.old_counter_state: # iterate over counterstate vars_old_counter_state = vars(self.old_counter_state) @@ -242,7 +242,7 @@ def detect_modbus_usb_port(self) -> str: return "/dev/serial0" @staticmethod - def get_cp_num(local_charge_point_num) -> int: + def get_cp_num(local_charge_point_num: int) -> int: try: if local_charge_point_num == 1: return int(re.sub(r'\D', '', ramdisk_read("parentCPlp1"))) @@ -263,7 +263,7 @@ def get_parent_wb() -> str: class IsssChargepoint: - def __init__(self, serial_client, local_charge_point_num) -> None: + def __init__(self, serial_client: ModbusSerialClient_, local_charge_point_num: int) -> None: self.local_charge_point_num = local_charge_point_num if local_charge_point_num == 1: try: @@ -278,7 +278,7 @@ def __init__(self, serial_client, local_charge_point_num) -> None: self.update_state = UpdateState(self.module) self.old_plug_state = False - def update(self): + def update(self) -> None: def __thread_active(thread: Optional[threading.Thread]) -> bool: if thread: return thread.is_alive() From f867b3517c3549509e444f3cd8f49ad22d694b85 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Mon, 31 Oct 2022 12:32:37 +0100 Subject: [PATCH 019/201] Fix openWB Pro cp 4-8 --- loadvars.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/loadvars.sh b/loadvars.sh index e7af10f0a7..94f3c574ce 100755 --- a/loadvars.sh +++ b/loadvars.sh @@ -719,7 +719,7 @@ loadvars(){ if [[ "$evseconlp4" == "extopenwb" ]]; then timeout 3 modules/extopenwb/main.sh 4 "$chargep4ip" "$chargep4cp" || true elif [[ "$evseconlp4" == "owbpro" ]]; then - timeout 3 modules/owbpro/main.sh 4 "$chargep4ip" || true + timeout 3 modules/owbpro/main.sh 4 "$owbpro4ip" || true else timeout 3 modules/mpm3pmlllp4/main.sh || true fi @@ -759,7 +759,7 @@ loadvars(){ if [[ "$evseconlp5" == "extopenwb" ]]; then timeout 3 modules/extopenwb/main.sh 5 "$chargep5ip" "$chargep5cp" || true elif [[ "$evseconlp5" == "owbpro" ]]; then - timeout 3 modules/owbpro/main.sh 5 "$chargep5ip" || true + timeout 3 modules/owbpro/main.sh 5 "$owbpro5ip" || true else timeout 3 modules/mpm3pmlllp5/main.sh || true fi @@ -799,7 +799,7 @@ loadvars(){ if [[ "$evseconlp6" == "extopenwb" ]]; then timeout 3 modules/extopenwb/main.sh 6 "$chargep6ip" "$chargep6cp" || true elif [[ "$evseconlp6" == "owbpro" ]]; then - timeout 3 modules/owbpro/main.sh 6 "$chargep6ip" || true + timeout 3 modules/owbpro/main.sh 6 "$owbpro6ip" || true else timeout 3 modules/mpm3pmlllp6/main.sh || true fi @@ -839,7 +839,7 @@ loadvars(){ if [[ "$evseconlp7" == "extopenwb" ]]; then timeout 3 modules/extopenwb/main.sh 7 "$chargep7ip" "$chargep7cp" || true elif [[ "$evseconlp7" == "owbpro" ]]; then - timeout 3 modules/owbpro/main.sh 7 "$chargep7ip" || true + timeout 3 modules/owbpro/main.sh 7 "$owbpro7ip" || true else timeout 3 modules/mpm3pmlllp7/main.sh || true fi @@ -878,7 +878,7 @@ loadvars(){ if [[ "$evseconlp8" == "extopenwb" ]]; then timeout 3 modules/extopenwb/main.sh 8 "$chargep8ip" "$chargep8cp" || true elif [[ "$evseconlp8" == "owbpro" ]]; then - timeout 3 modules/owbpro/main.sh 8 "$chargep8ip" || true + timeout 3 modules/owbpro/main.sh 8 "$owbpro8ip" || true else timeout 3 modules/mpm3pmlllp8/main.sh || true fi From 2448e8bf21f2893dad7abc4418ef85d57cc99934 Mon Sep 17 00:00:00 2001 From: okaegi Date: Mon, 31 Oct 2022 18:06:05 +0100 Subject: [PATCH 020/201] =?UTF-8?q?logging=20=C3=BCber=20python=20logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Es wurde das für ratiotherm das python logging modul verwendet. Sofern die Anwendung okay ist, kann ich die anderen Geräte auch umstellen. Dafür habe ich ein zentrales SmartUtils.py geschrieben, welches dann in allen Smarthomegeräten angewendet wird. Da der PYTHONPATH bereits auf ..openWB/packages/ verweist, habe ich die smartutils.py im Verzeichnis ..openWB/packages/modules/common abgelegt so dass es im gesamten .../Smarthome/... Folder importiert werden kann --- modules/smarthome/ratiotherm/off.py | 23 +++++++------------- modules/smarthome/ratiotherm/on.py | 22 ++++++-------------- modules/smarthome/ratiotherm/watt.py | 30 +++++++++------------------ packages/modules/common/smartutils.py | 13 ++++++++++++ 4 files changed, 36 insertions(+), 52 deletions(-) create mode 100644 packages/modules/common/smartutils.py diff --git a/modules/smarthome/ratiotherm/off.py b/modules/smarthome/ratiotherm/off.py index 98daf177b1..b346440aa2 100644 --- a/modules/smarthome/ratiotherm/off.py +++ b/modules/smarthome/ratiotherm/off.py @@ -1,30 +1,21 @@ #!/usr/bin/python3 -import sys import os -import time +import sys +import logging +from modules.common.smartutils import initlog -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm off.py", named_tuple) devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) bp = '/var/www/html/openWB/ramdisk/smarthome_device_' # standard -file_string = bp + str(devicenumber) + '_ratiotherm.log' file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' -if os.path.isfile(file_string): - pass -else: - with open(file_string, 'w') as f: - print('ratiotherm start log', file=f) -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' - % (time_string, devicenumber, ipadr, uberschuss), file=f) +initlog("ratiotherm", devicenumber) +log = logging.getLogger("ratiotherm") aktpower = 0 -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s Akt Leistung %6d' - % (time_string, devicenumber, ipadr, aktpower), file=f) +log.info(" off devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" + % (devicenumber, ipadr, uberschuss, aktpower)) pvmodus = 0 if os.path.isfile(file_stringpv): with open(file_stringpv, 'r') as f: diff --git a/modules/smarthome/ratiotherm/on.py b/modules/smarthome/ratiotherm/on.py index 3284482859..162cf435e3 100644 --- a/modules/smarthome/ratiotherm/on.py +++ b/modules/smarthome/ratiotherm/on.py @@ -1,9 +1,7 @@ #!/usr/bin/python3 import sys -import os -import time -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm on.py", named_tuple) +import logging +from modules.common.smartutils import initlog devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) @@ -11,21 +9,13 @@ # standard # lesen # own log -file_string = bp + str(devicenumber) + '_ratiotherm.log' +initlog("ratiotherm", devicenumber) +log = logging.getLogger("ratiotherm") file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' -if os.path.isfile(file_string): - pass -else: - with open(file_string, 'w') as f: - print('ratiotherm start log', file=f) -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' - % (time_string, devicenumber, ipadr, uberschuss), file=f) aktpower = 0 -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s Akt Leistung %6d ' % - (time_string, devicenumber, ipadr, aktpower), file=f) +log.info(" on devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" + % (devicenumber, ipadr, uberschuss, aktpower)) with open(file_stringpv, 'w') as f: f.write(str(1)) count1 = 999 diff --git a/modules/smarthome/ratiotherm/watt.py b/modules/smarthome/ratiotherm/watt.py index 276f1776e8..f16052ee71 100644 --- a/modules/smarthome/ratiotherm/watt.py +++ b/modules/smarthome/ratiotherm/watt.py @@ -1,21 +1,21 @@ #!/usr/bin/python3 import sys import os -import time import json from pymodbus.payload import BinaryPayloadBuilder, Endian from pymodbus.client.sync import ModbusTcpClient -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S ratiotherm watty.py", named_tuple) +import logging +from modules.common.smartutils import initlog devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) forcesend = int(sys.argv[4]) +initlog("ratiotherm", devicenumber) +log = logging.getLogger("ratiotherm") # forcesend = 0 default acthor time period applies # forcesend = 1 default overwritten send now # forcesend = 9 default overwritten no send bp = '/var/www/html/openWB/ramdisk/smarthome_device_' -file_string = bp + str(devicenumber) + '_ratiotherm.log' file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' file_stringcount5 = bp + str(devicenumber) + '_count5' @@ -70,18 +70,10 @@ with open(file_stringpv, 'w') as f: f.write(str(pvmodus)) if count1 < 3: - if os.path.isfile(file_string): - pass - else: - with open(file_string, 'w') as f: - print('ratiotherm start log', file=f) - with open(file_string, 'a') as f: - print('%s Nr %s ipadr %s ueberschuss %6d Akt Leistung %6d' - % (time_string, devicenumber, ipadr, uberschuss, aktpower), - file=f) - print('%s Nr %s ipadr %s neupower %6d pvmodus %1d modbusw %1d' - % (time_string, devicenumber, ipadr, neupower, pvmodus, - modbuswrite), file=f) + log.info(" watt devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" + % (devicenumber, ipadr, uberschuss, aktpower)) + log.info(" watt devicenr %s ipadr %s neupower %6d pvmodus %1d modbusw %1d" + % (devicenumber, ipadr, neupower, pvmodus, modbuswrite)) # modbus write if modbuswrite == 1: # andernfalls absturz bei negativen Zahlen @@ -92,10 +84,8 @@ client = ModbusTcpClient(ipadr, port=502) client.write_register(100, pay[0], unit=1) if count1 < 3: - with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s written %6d %#4X' % - (time_string, devicenumber, ipadr, pay[0], pay[0]), - file=f) + log.info(" watt devicenr %s ipadr %s written %6d %#4X" + % (devicenumber, ipadr, pay[0], pay[0])) else: if pvmodus == 99: pvmodus = 0 diff --git a/packages/modules/common/smartutils.py b/packages/modules/common/smartutils.py new file mode 100644 index 0000000000..7ced74baf2 --- /dev/null +++ b/packages/modules/common/smartutils.py @@ -0,0 +1,13 @@ +import logging + + +def initlog(name, devicenumber): + log = logging.getLogger(name) + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + log.setLevel(logging.DEBUG) + fname = '/var/www/html/openWB/ramdisk/smarthome_device_' + fname += str(devicenumber) + '_' + str(name) + '.log' + fh = logging.FileHandler(fname, encoding='utf8') + fh.setLevel(logging.DEBUG) + fh.setFormatter(formatter) + log.addHandler(fh) From f741687235ace4d367ac87034ce99ab55fe09f0e Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:58:24 +0100 Subject: [PATCH 021/201] Change for new Mercedes EQ Logins https://developer.mercedes-benz.com/content-page/migration_2022 --- modules/soc_eq/auth.py | 3 ++- modules/soc_eq/soc.py | 8 +++++--- web/settings/modulconfiglp.php | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/soc_eq/auth.py b/modules/soc_eq/auth.py index b8068c1773..834a44cff0 100755 --- a/modules/soc_eq/auth.py +++ b/modules/soc_eq/auth.py @@ -54,7 +54,8 @@ def printHtml(message): fd.close() -tok_url = "https://id.mercedes-benz.com/as/token.oauth2" +#tok_url = "https://id.mercedes-benz.com/as/token.oauth2" +tok_url = "https://ssoalpha.dvb.corpinter.net/v1/token" data = {'grant_type': 'authorization_code', 'code': str(code), 'redirect_uri': callback} #call API to get Access/Refresh tokens diff --git a/modules/soc_eq/soc.py b/modules/soc_eq/soc.py index 1bef889237..e962481682 100755 --- a/modules/soc_eq/soc.py +++ b/modules/soc_eq/soc.py @@ -19,7 +19,7 @@ Debug = int(os.environ.get('debug')) myPid = str(os.getpid()) -tok_url = "https://id.mercedes-benz.com/as/token.oauth2" +tok_url = "https://ssoalpha.dvb.corpinter.net/v1/token" soc_url = "https://api.mercedes-benz.com/vehicledata/v2/vehicles/"+VIN+"/containers/electricvehicle" @@ -94,7 +94,7 @@ def handleResponse(what, status_code, text): expires_in = tok['expires_in'] fd.close() -#socDebugLog("Expire in: " str((int(expires_in)-int(time.time()))) +socDebugLog("Token expires in: " +str((int(expires_in)-int(time.time())))+"s") if int(expires_in) < int(time.time()): #Access Token is exired @@ -125,11 +125,13 @@ def handleResponse(what, status_code, text): access_token = tok['access_token'] refresh_token = tok['refresh_token'] expires_in = tok['expires_in'] - 60 + int(time.time()) + id_token = tok['id_token'] + token_type = tok['token_type'] #write new tokens fd = open(moduledir + 'soc_eq_acc_lp' + str(ChargePoint),'w') - json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token}, fd) + json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token, 'id_token' : id_token, 'token_type' : token_type}, fd) fd.close() else: handleResponse("Refresh",ref.status_code,ref.text) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index 5a6cbf7b97..aad19ba314 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -1953,7 +1953,7 @@ function visibility_psa_soccalclp1() {
Wichtig: Nach dem Eintragen der Werte müssen diese gespeichert werden und danach einmalig der folgende Link aufgerufen werden: - " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden + " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden
@@ -3644,7 +3644,7 @@ function visibility_psa_soccalclp2() {
Wichtig: Nach dem Eintragen der Werte müssen diese gespeichert werden und danach einmalig der folgende Link aufgerufen werden
- " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden
+ " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden
From 03aa116deea051f981d2a8294dd00cdfcf3afb4d Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 1 Nov 2022 18:42:52 +0100 Subject: [PATCH 022/201] fixing indentication switch to 4 spaces --- modules/soc_eq/auth.py | 50 ++++---- modules/soc_eq/soc.py | 254 ++++++++++++++++++++--------------------- 2 files changed, 152 insertions(+), 152 deletions(-) diff --git a/modules/soc_eq/auth.py b/modules/soc_eq/auth.py index 834a44cff0..68c2bfe331 100755 --- a/modules/soc_eq/auth.py +++ b/modules/soc_eq/auth.py @@ -34,23 +34,23 @@ def printHtml(message): #get SoC module config from openWB cofig fd = open('/var/www/html/openWB/openwb.conf','r') for line in fd: - try: - printDebug("owb Conf: " + line,2 ) - (key, val) = line.rstrip().split("=") - if key == "debug": - Debug = int(val) - if key == "soc_eq_client_id_lp" + str(ChargePoint): - printDebug("Found Client ID: " + val ,1) - client_id = val - if key == "soc_eq_client_secret_lp" + str(ChargePoint): - printDebug("Found Client Secret: " + val ,1) - client_secret = val - if key == "soc_eq_cb_lp" + str(ChargePoint): - printDebug("Found callback URL: " + val ,1) - callback = val.replace("'","") - except: + try: + printDebug("owb Conf: " + line,2 ) + (key, val) = line.rstrip().split("=") + if key == "debug": + Debug = int(val) + if key == "soc_eq_client_id_lp" + str(ChargePoint): + printDebug("Found Client ID: " + val ,1) + client_id = val + if key == "soc_eq_client_secret_lp" + str(ChargePoint): + printDebug("Found Client Secret: " + val ,1) + client_secret = val + if key == "soc_eq_cb_lp" + str(ChargePoint): + printDebug("Found callback URL: " + val ,1) + callback = val.replace("'","") + except: - val = "" + val = "" fd.close() @@ -64,17 +64,17 @@ def printHtml(message): printDebug(act.url,1) if act.status_code == 200: - #valid Response - toks = json.loads(act.text) - access_token = toks['access_token'] - refresh_token = toks['refresh_token'] - expires_in = int(time.time()) + #valid Response + toks = json.loads(act.text) + access_token = toks['access_token'] + refresh_token = toks['refresh_token'] + expires_in = int(time.time()) - #write tokens to files + #write tokens to files - fd = open(moddir + 'soc_eq_acc_lp' + str(ChargePoint),'w') - json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token}, fd) - fd.close() + fd = open(moddir + 'soc_eq_acc_lp' + str(ChargePoint),'w') + json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token}, fd) + fd.close() if act.status_code == 200: printHtml( "Anmeldung erfolgreich!" ) diff --git a/modules/soc_eq/soc.py b/modules/soc_eq/soc.py index e962481682..03e7182ec1 100755 --- a/modules/soc_eq/soc.py +++ b/modules/soc_eq/soc.py @@ -27,69 +27,69 @@ range = None def socDebugLog(message): - local_time = datetime.now(timezone.utc).astimezone() - print(local_time.strftime(format = "%Y-%m-%d %H:%M:%S") +": Lp" +ChargePoint + ": PID:"+ myPid + ": " + message) + local_time = datetime.now(timezone.utc).astimezone() + print(local_time.strftime(format = "%Y-%m-%d %H:%M:%S") +": Lp" +ChargePoint + ": PID:"+ myPid + ": " + message) def handleResponse(what, status_code, text): - if status_code == 204: - # this is not an error code. Nothing to fetch so nothing to update and no reason to exit(1) - socDebugLog(what + " Request Code: " + str(status_code) + " (no data is available for the resource)") - socDebugLog(text) - elif status_code == 400: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Bad Request)") - socDebugLog(text) - exit(1) - elif status_code == 401: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Invalid or missing authorization in header)") - socDebugLog(text) - exit(1) - elif status_code == 402: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Payment required)") - socDebugLog(text) - exit(1) - elif status_code == 403: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Forbidden)") - socDebugLog(text) - exit(1) - elif status_code == 404: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The requested resource was not found, e.g.: the selected vehicle could not be found)") - socDebugLog(text) - exit(1) - elif status_code == 429: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The service received too many requests in a given amount of time)") - socDebugLog(text) - exit(1) - elif status_code == 500: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The service received too many requests in a given amount of time)") - socDebugLog(text) - exit(1) - elif status_code == 503: - socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The server is unable to service the request due to a temporary unavailability condition)") - socDebugLog(text) - exit(1) - else: - socDebugLog(what + " Request fehlgeschlagen unbekannter Code: " + str(status_code)) - socDebugLog(text) - exit(1) + if status_code == 204: + # this is not an error code. Nothing to fetch so nothing to update and no reason to exit(1) + socDebugLog(what + " Request Code: " + str(status_code) + " (no data is available for the resource)") + socDebugLog(text) + elif status_code == 400: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Bad Request)") + socDebugLog(text) + exit(1) + elif status_code == 401: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Invalid or missing authorization in header)") + socDebugLog(text) + exit(1) + elif status_code == 402: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Payment required)") + socDebugLog(text) + exit(1) + elif status_code == 403: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (Forbidden)") + socDebugLog(text) + exit(1) + elif status_code == 404: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The requested resource was not found, e.g.: the selected vehicle could not be found)") + socDebugLog(text) + exit(1) + elif status_code == 429: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The service received too many requests in a given amount of time)") + socDebugLog(text) + exit(1) + elif status_code == 500: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The service received too many requests in a given amount of time)") + socDebugLog(text) + exit(1) + elif status_code == 503: + socDebugLog(what + " Request fehlgeschlagen Code: " + str(status_code) + " (The server is unable to service the request due to a temporary unavailability condition)") + socDebugLog(text) + exit(1) + else: + socDebugLog(what + " Request fehlgeschlagen unbekannter Code: " + str(status_code)) + socDebugLog(text) + exit(1) if Debug >= 1: - socDebugLog("Debug Level: " + str(Debug)) + socDebugLog("Debug Level: " + str(Debug)) if Debug >= 1: - socDebugLog("client: " + client_id) + socDebugLog("client: " + client_id) if Debug >= 2: - socDebugLog("SOC URL: " + soc_url) + socDebugLog("SOC URL: " + soc_url) #Get Access token from file fd = open(moduledir + 'soc_eq_acc_lp' + str(ChargePoint),'r') try: - tok = json.load(fd) - access_token = tok['access_token'] + tok = json.load(fd) + access_token = tok['access_token'] except ValueError: - socDebugLog("ERROR: Access Token not found. Please use Link 'HIER bei Mercedes Me' anmelden in LP Configuration") - exit(3) + socDebugLog("ERROR: Access Token not found. Please use Link 'HIER bei Mercedes Me' anmelden in LP Configuration") + exit(3) refresh_token = tok['refresh_token'] expires_in = tok['expires_in'] fd.close() @@ -97,103 +97,103 @@ def handleResponse(what, status_code, text): socDebugLog("Token expires in: " +str((int(expires_in)-int(time.time())))+"s") if int(expires_in) < int(time.time()): - #Access Token is exired - if Debug >= 1: - socDebugLog("Acc Token Expired") + #Access Token is exired + if Debug >= 1: + socDebugLog("Acc Token Expired") - #get new Access Token with referesh token - data = {'grant_type': 'refresh_token', 'refresh_token': refresh_token } - ref = requests.post(tok_url, data=data, verify=True, allow_redirects=False, auth=(client_id, client_secret), timeout=req_timeout) - if Debug >= 1: - socDebugLog("Refresh Token Call:" + str(ref.status_code)) - socDebugLog("Refresh Token Text:" + str(ref.text)) - - - #write HTTP reponse code to file - try: - fd = open(ramdiskdir + 'soc_eq_lastresp','w') - fd.write(str(ref.status_code)) - fd.close() - except: - fd.close() - - - if ref.status_code == 200: - #valid response - tok = json.loads(ref.text) - - access_token = tok['access_token'] - refresh_token = tok['refresh_token'] - expires_in = tok['expires_in'] - 60 + int(time.time()) - id_token = tok['id_token'] - token_type = tok['token_type'] - - #write new tokens - - fd = open(moduledir + 'soc_eq_acc_lp' + str(ChargePoint),'w') - json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token, 'id_token' : id_token, 'token_type' : token_type}, fd) - fd.close() - else: - handleResponse("Refresh",ref.status_code,ref.text) + #get new Access Token with referesh token + data = {'grant_type': 'refresh_token', 'refresh_token': refresh_token } + ref = requests.post(tok_url, data=data, verify=True, allow_redirects=False, auth=(client_id, client_secret), timeout=req_timeout) + if Debug >= 1: + socDebugLog("Refresh Token Call:" + str(ref.status_code)) + socDebugLog("Refresh Token Text:" + str(ref.text)) + + + #write HTTP reponse code to file + try: + fd = open(ramdiskdir + 'soc_eq_lastresp','w') + fd.write(str(ref.status_code)) + fd.close() + except: + fd.close() + + + if ref.status_code == 200: + #valid response + tok = json.loads(ref.text) + + access_token = tok['access_token'] + refresh_token = tok['refresh_token'] + expires_in = tok['expires_in'] - 60 + int(time.time()) + id_token = tok['id_token'] + token_type = tok['token_type'] + + #write new tokens + + fd = open(moduledir + 'soc_eq_acc_lp' + str(ChargePoint),'w') + json.dump({'expires_in' : expires_in, 'refresh_token' : refresh_token, 'access_token' : access_token, 'id_token' : id_token, 'token_type' : token_type}, fd) + fd.close() + else: + handleResponse("Refresh",ref.status_code,ref.text) #call API for SoC header = {'authorization': 'Bearer ' + access_token} try: - req_soc = requests.get(soc_url, headers=header, verify=True) - #req_soc = requests.get(soc_url, headers=header, verify=True, timeout=req_timeout) + req_soc = requests.get(soc_url, headers=header, verify=True) + #req_soc = requests.get(soc_url, headers=header, verify=True, timeout=req_timeout) except Timeout: - socDebugLog("Soc Request Timed Out") - exit(2) + socDebugLog("Soc Request Timed Out") + exit(2) except RequestException: - socDebugLog("Soc Request Request Exception occured " + soc_url) - exit(2) + socDebugLog("Soc Request Request Exception occured " + soc_url) + exit(2) if Debug >= 1: - socDebugLog("SOC Request: " + str(req_soc.status_code)) - socDebugLog("SOC Response: " + req_soc.text) + socDebugLog("SOC Request: " + str(req_soc.status_code)) + socDebugLog("SOC Response: " + req_soc.text) #write HTTP reponse code to file try: - fd = open(ramdiskdir + 'soc_eq_lastresp','w') - fd.write(str(req_soc.status_code)) - fd.close() + fd = open(ramdiskdir + 'soc_eq_lastresp','w') + fd.write(str(req_soc.status_code)) + fd.close() except: - fd.close() + fd.close() if req_soc.status_code == 200: - #valid Response - try: - res = json.loads(req_soc.text) - except JSONDecodeError: - socDebugLog("Soc Response NO VALID JSON " + req_soc.text) - exit(2) - - #Extract SoC value and write to file - for entry in res: - for values in entry: - if values == "soc": - soc = entry[values]['value'] - elif values == "rangeelectric": - range = entry[values]['value'] - else: - socDebugLog("unknown entry: " + entry) - if not soc: - socDebugLog("SoC Value not filled " + req_soc.text) - soc = "0" - if not range: - socDebugLog("RangeElectric Value not filled " + req_soc.text) - range = "0" - socDebugLog("SOC: " + soc + " RANGE: " + range) - fd = open(soc_file,'w') - fd.write(str(soc)) - fd.close() - + #valid Response + try: + res = json.loads(req_soc.text) + except JSONDecodeError: + socDebugLog("Soc Response NO VALID JSON " + req_soc.text) + exit(2) + + #Extract SoC value and write to file + for entry in res: + for values in entry: + if values == "soc": + soc = entry[values]['value'] + elif values == "rangeelectric": + range = entry[values]['value'] + else: + socDebugLog("unknown entry: " + entry) + if not soc: + socDebugLog("SoC Value not filled " + req_soc.text) + soc = "0" + if not range: + socDebugLog("RangeElectric Value not filled " + req_soc.text) + range = "0" + socDebugLog("SOC: " + soc + " RANGE: " + range) + fd = open(soc_file,'w') + fd.write(str(soc)) + fd.close() + else: - handleResponse("SoC",req_soc.status_code,req_soc.text) + handleResponse("SoC",req_soc.status_code,req_soc.text) if Debug >= 2: - socDebugLog("SoC EQ Ende ohne Fehler") + socDebugLog("SoC EQ Ende ohne Fehler") exit(0) From d47290cb17b6eeb5a9244d51856f728d28bacf42 Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:08:35 +0100 Subject: [PATCH 023/201] fix indent --- web/settings/modulconfiglp.php | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index aad19ba314..d0ac655e91 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -388,7 +388,7 @@ function visibility_twcmanagerlp1_connection() { visibility_twcmanagerlp1_connection(); }); - visibility_twcmanagerlp1_connection(); + visibility_twcmanagerlp1_connection(); }); @@ -1142,28 +1142,28 @@ function visibility_kia_advanced() {
-
-
- Anmeldedaten fuer den Aiways U5 -
-
- -
- - - Aiways Account Name (nicht die E-Mail-Adresse) - -
-
-
- -
+
+
+ Anmeldedaten fuer den Aiways U5 +
+
+ +
+ + + Aiways Account Name (nicht die E-Mail-Adresse) + +
+
+
+ +
- - Aiways Passwort - -
-
+ + Aiways Passwort + +
+
From 676096230715c79c3107c1480e2f48eb1400985e Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:17:27 +0100 Subject: [PATCH 024/201] fix indent --- web/settings/modulconfiglp.php | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index d0ac655e91..6a25314645 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -1165,25 +1165,25 @@ function visibility_kia_advanced() {
- -
- - - VIN des Fahrzeugs - -
-
-
- -
+ +
+ + + VIN des Fahrzeugs + +
+
+
+ +
- - Verkürzt das Abfrageintervall beim Laden auf xx Minuten - -
-
-
-
+ + Verkürzt das Abfrageintervall beim Laden auf xx Minuten + +
+
+ +
@@ -3134,16 +3134,16 @@ function visibility_twcmanagerlp2_connection() {
-
-
- -
- - - Aiways Account Name (nicht die E-Mail-Adresse) - -
-
+
+
+ +
+ + + Aiways Account Name (nicht die E-Mail-Adresse) + +
+
From e01328842e111b6bc924127b6dd836d840807ff9 Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:23:10 +0100 Subject: [PATCH 025/201] fix indent --- web/settings/modulconfiglp.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index 6a25314645..c059494b59 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -2301,8 +2301,8 @@ function display_socmodul() { showSection('#socmkia'); } if($('#socmodul').val() == 'soc_aiways') { - showSection('#socaiways'); - } + showSection('#socaiways'); + } if($('#socmodul').val() == 'soc_audi') { showSection('#socoldevccwarning'); showSection('#socmaudi'); @@ -3172,7 +3172,7 @@ function visibility_twcmanagerlp2_connection() {
-
+
@@ -4090,7 +4090,7 @@ function display_lp2() { case "openwb series1/2 duo v2": showSection('#openwb12s1v2'); break; - default: + default: showSection('#evseconmbs1'); showSection('#llmodullp2'); display_llmp2(); @@ -5030,7 +5030,7 @@ function display_lastmanagementlp() {
- Sie befinden sich hier: Einstellungen/Modulkonfiguration + Sie befinden sich hier: Einstellungen/Modulkonfiguration
From fad8528ad71033db1bf986cb7cfb106aae90d398 Mon Sep 17 00:00:00 2001 From: benderl Date: Wed, 2 Nov 2022 08:05:46 +0100 Subject: [PATCH 026/201] Update on.py fix newline at end of file --- modules/smarthome/http/on.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smarthome/http/on.py b/modules/smarthome/http/on.py index a28a231078..8888301446 100644 --- a/modules/smarthome/http/on.py +++ b/modules/smarthome/http/on.py @@ -24,4 +24,4 @@ f = open( file_string , 'w') print ('%s devicenr %s url %s' % (time_string,devicenumber,url),file=f) f.close() -urllib.request.urlopen(url, timeout=5) \ No newline at end of file +urllib.request.urlopen(url, timeout=5) From 31c18769adb1fa1090127c6906d8047fc8dd0568 Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Wed, 2 Nov 2022 14:46:27 +0100 Subject: [PATCH 027/201] Mercedes New Authentication fix for LP2 --- web/settings/modulconfiglp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index c059494b59..7a1cd78467 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -3644,7 +3644,7 @@ function visibility_psa_soccalclp2() {
Wichtig: Nach dem Eintragen der Werte müssen diese gespeichert werden und danach einmalig der folgende Link aufgerufen werden
- " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden
+ " target="_blank" rel="noopener noreferrer">HIER bei Mercedes Me anmelden
From 37a4184c0d4d98ad204f2a2043c9bab72f1cdd44 Mon Sep 17 00:00:00 2001 From: okaegi Date: Wed, 2 Nov 2022 17:23:23 +0100 Subject: [PATCH 028/201] gemeinsames Smarthome Verzeichniss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neu wird das ...packages\smarthome\ Verzeichnis für gemeinsame import files verwendet. --- modules/smarthome/ratiotherm/off.py | 3 +-- modules/smarthome/ratiotherm/on.py | 2 +- modules/smarthome/ratiotherm/watt.py | 2 +- .../{modules/common/smartutils.py => smarthome/smartlog.py} | 0 4 files changed, 3 insertions(+), 4 deletions(-) rename packages/{modules/common/smartutils.py => smarthome/smartlog.py} (100%) diff --git a/modules/smarthome/ratiotherm/off.py b/modules/smarthome/ratiotherm/off.py index b346440aa2..677503eb2b 100644 --- a/modules/smarthome/ratiotherm/off.py +++ b/modules/smarthome/ratiotherm/off.py @@ -2,8 +2,7 @@ import os import sys import logging -from modules.common.smartutils import initlog - +from smarthome.smartlog import initlog devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) diff --git a/modules/smarthome/ratiotherm/on.py b/modules/smarthome/ratiotherm/on.py index 162cf435e3..568eec1c30 100644 --- a/modules/smarthome/ratiotherm/on.py +++ b/modules/smarthome/ratiotherm/on.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 import sys import logging -from modules.common.smartutils import initlog +from smarthome.smartlog import initlog devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) diff --git a/modules/smarthome/ratiotherm/watt.py b/modules/smarthome/ratiotherm/watt.py index f16052ee71..8dce8efa36 100644 --- a/modules/smarthome/ratiotherm/watt.py +++ b/modules/smarthome/ratiotherm/watt.py @@ -5,7 +5,7 @@ from pymodbus.payload import BinaryPayloadBuilder, Endian from pymodbus.client.sync import ModbusTcpClient import logging -from modules.common.smartutils import initlog +from smarthome.smartlog import initlog devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) diff --git a/packages/modules/common/smartutils.py b/packages/smarthome/smartlog.py similarity index 100% rename from packages/modules/common/smartutils.py rename to packages/smarthome/smartlog.py From 4e66d3a65265cffe9dd8fa74fae0398aeb30da7f Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 3 Nov 2022 08:13:38 +0100 Subject: [PATCH 029/201] Fix fault topic --- packages/modules/common/fault_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/common/fault_state.py b/packages/modules/common/fault_state.py index fec941f5c3..e87fd7d93d 100644 --- a/packages/modules/common/fault_state.py +++ b/packages/modules/common/fault_state.py @@ -51,7 +51,7 @@ def store_error(self, component_info: ComponentInfo) -> None: else: prefix += str(component_info.id) + "/fault" else: - prefix += "f" + prefix += "fault" pub.pub_single(prefix + "Str", self.fault_str, hostname=component_info.hostname) pub.pub_single(prefix + "State", self.fault_state.value, hostname=component_info.hostname) if "chargepoint" in component_info.type: From dbede0ef61f1646fcfe87a2000edfdedd43e0800 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 3 Nov 2022 11:13:07 +0100 Subject: [PATCH 030/201] recreate log file if missing --- runs/atreboot.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runs/atreboot.sh b/runs/atreboot.sh index 7623eba9de..65eb722946 100755 --- a/runs/atreboot.sh +++ b/runs/atreboot.sh @@ -1,6 +1,12 @@ #!/bin/bash OPENWBBASEDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) LOGFILE="/var/log/openWB.log" +# always check for existing log file! +if [[ ! -f $LOGFILE ]]; then + sudo touch $LOGFILE + sudo chmod 777 $LOGFILE +fi + . "$OPENWBBASEDIR/helperFunctions.sh" at_reboot() { From 4e31a5d8ff1bba8fceb289aec745ae8315eff910 Mon Sep 17 00:00:00 2001 From: okaegi <72255431+okaegi@users.noreply.github.com> Date: Fri, 11 Nov 2022 14:38:28 +0100 Subject: [PATCH 031/201] =?UTF-8?q?E3dc=20nun=20komplett=20auf=20openwb=20?= =?UTF-8?q?V2.0=20umgestellt,=20ersetzt=20E3dc0010=20Branch=20vollst=C3=A4?= =?UTF-8?q?ndig=20(#2485)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * E3dc nun komplett auf openwb V2.0 umgestellt, ersetzt E3DC0010 branch Vorschläge von Yannik umgesetzt, neuen Branch eröffnet, ersetzt den E3dc0010 * update... Yanniks Anregungen umgesetzt * korrektur... Yanniks Anregungen umgesetzt.... * Korrektur Vorschläge umgesetzt.... * Korrektur * Korrektur.. * Korrektur.. --- modules/bezug_e3dc/e3dc.py | 44 ---------- modules/bezug_e3dc/main.sh | 2 +- modules/speicher_e3dc/e3dc.py | 78 ------------------ modules/speicher_e3dc/main.sh | 2 +- packages/modules/e3dc/bat.py | 48 +++++++++++ packages/modules/e3dc/config.py | 71 ++++++++++++++++ packages/modules/e3dc/counter.py | 53 ++++++++++++ packages/modules/e3dc/device.py | 133 ++++++++++++++++++++++++++++++ packages/modules/e3dc/inverter.py | 67 +++++++++++++++ 9 files changed, 374 insertions(+), 124 deletions(-) delete mode 100755 modules/bezug_e3dc/e3dc.py delete mode 100755 modules/speicher_e3dc/e3dc.py create mode 100644 packages/modules/e3dc/bat.py create mode 100644 packages/modules/e3dc/config.py create mode 100644 packages/modules/e3dc/counter.py create mode 100644 packages/modules/e3dc/device.py create mode 100644 packages/modules/e3dc/inverter.py diff --git a/modules/bezug_e3dc/e3dc.py b/modules/bezug_e3dc/e3dc.py deleted file mode 100755 index eff90a573f..0000000000 --- a/modules/bezug_e3dc/e3dc.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python -import logging -from typing import List - -from pymodbus.constants import Endian - -from helpermodules.cli import run_using_positional_cli_args -from modules.common.component_state import CounterState -from modules.common.modbus import ModbusTcpClient_, ModbusDataType -from modules.common.simcount import sim_count -from modules.common.store import get_counter_value_store - -log = logging.getLogger("E3DC EVU") - - -def update(ipaddress: str): - log.debug("Beginning update") - with ModbusTcpClient_(ipaddress, port=502) as client: - # 40074 EVU Punkt negativ -> Einspeisung in Watt - power_all = client.read_holding_registers(40073, ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) - # 40130 Phasenleistung in Watt - # max 6 Leistungsmesser verbaut ab 410105, typ 1 ist evu - # bei den meisten e3dc auf 40128 - # for i in range (40104,40132,4): - for i in range(40128, 40103, -4): - # powers = client.read_holding_registers(40129, [ModbusDataType.INT_16] * 3, unit=1) - powers = client.read_holding_registers(i, [ModbusDataType.INT_16] * 4, unit=1) - log.debug("I: %d, p[0] typ %d p[1] a1 %d p[2] a2 %d p[3] a3 %d", - i, powers[0], powers[1], powers[2], powers[3]) - if powers[0] == 1: - log.debug("Evu Leistungsmessung gefunden") - break - counter_import, counter_export = sim_count(power_all, prefix="bezug") - get_counter_value_store(1).set(CounterState( - imported=counter_import, - exported=counter_export, - power=power_all, - powers=powers[1:] - )) - log.debug("Update completed successfully") - - -def main(argv: List[str]): - run_using_positional_cli_args(update, argv) diff --git a/modules/bezug_e3dc/main.sh b/modules/bezug_e3dc/main.sh index e07b72d7e8..70999d8fdf 100755 --- a/modules/bezug_e3dc/main.sh +++ b/modules/bezug_e3dc/main.sh @@ -10,7 +10,7 @@ else MYLOGFILE="${RAMDISKDIR}/evu.log" fi -bash "$OPENWBBASEDIR/packages/legacy_run.sh" "bezug_e3dc.e3dc" "${e3dcip}" >>"${MYLOGFILE}" 2>&1 +bash "$OPENWBBASEDIR/packages/legacy_run.sh" "modules.e3dc.device" "counter" "$e3dcip" "1">>"${MYLOGFILE}" 2>&1 ret=$? openwbDebugLog ${DMOD} 2 "EVU RET: ${ret}" diff --git a/modules/speicher_e3dc/e3dc.py b/modules/speicher_e3dc/e3dc.py deleted file mode 100755 index 8ebf01d9f6..0000000000 --- a/modules/speicher_e3dc/e3dc.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python3 -import logging -from typing import Iterable, List - -from pymodbus.constants import Endian - -from helpermodules.cli import run_using_positional_cli_args -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.component_state import InverterState, BatState -from modules.common.fault_state import ComponentInfo -from modules.common.modbus import ModbusTcpClient_, ModbusDataType -from modules.common.simcount import sim_count -from modules.common.store import get_inverter_value_store, get_bat_value_store -from modules.common.store.ramdisk import files - -log = logging.getLogger("E3DC Battery") - - -def update_e3dc_battery(addresses: Iterable[str], read_external: int, pv_other: bool): - soc = 0 - count = 0 - battery_power = 0 - # pv_external - > pv Leistung die als externe Produktion an e3dc angeschlossen ist - # nur auslesen wenn als relevant parametrisiert (read_external = 1) , sonst doppelte Auslesung - pv_external = 0 - # pv -> pv Leistung die direkt an e3dc angeschlossen ist - pv = 0 - for address in addresses: - log.debug("Battery Ip: %s, read_external %d pv_other %s", address, read_external, pv_other) - count += 1 - with ModbusTcpClient_(address, port=502) as client: - # 40082 SoC - soc += client.read_holding_registers(40082, ModbusDataType.INT_16, unit=1) - # 40069 Speicherleistung - battery_power += client.read_holding_registers(40069, - ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) - # 40067 PV Leistung - pv += (client.read_holding_registers(40067, ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) * -1) - if read_external == 1: - # 40075 externe PV Leistung - pv_external += client.read_holding_registers(40075, - ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) - soc = soc / count - log.debug("Battery soc %d battery_power %d pv %d pv_external %d count ip %d", - soc, battery_power, pv, pv_external, count) - counter_import, counter_export = sim_count(battery_power, prefix="speicher") - get_bat_value_store(1).set(BatState(power=battery_power, soc=soc, imported=counter_import, exported=counter_export)) - # pv_other sagt aus, ob WR definiert ist, und dessen PV Leistung auch gilt - # wenn 0 gilt nur PV und pv_external aus e3dc - pv_total = pv + pv_external - # Wenn wr1 nicht definiert ist, gilt nur die PV Leistung die hier im Modul ermittelt wurde - # als gesamte PV Leistung für wr1 - if not pv_other or pv_total != 0: - # Wenn wr1 definiert ist, gilt die bestehende PV Leistung aus Wr1 und das was hier im Modul ermittelt wurde - # als gesamte PV Leistung für wr1 - if pv_other: - try: - pv_total = pv_total + files.pv[0].power.read() - except: - pass - log.debug("wr update pv_other %s pv_total %d", pv_other, pv_total) - _, counter_pv = sim_count(pv_total, prefix="pv") - get_inverter_value_store(1).set(InverterState(exported=counter_pv, power=pv_total)) - - -def update(address1: str, address2: str, read_external: int, pvmodul: str): - # read_external is 0 or 1 - log.debug("Beginning update") - addresses = [address for address in [address1, address2] if address != "none"] - pv_other = pvmodul != "none" - bat_info = ComponentInfo(None, "E3DC", "bat") - with SingleComponentUpdateContext(bat_info): - update_e3dc_battery(addresses, read_external, pv_other) - log.debug("Update completed successfully") - - -def main(argv: List[str]): - run_using_positional_cli_args(update, argv) diff --git a/modules/speicher_e3dc/main.sh b/modules/speicher_e3dc/main.sh index 3506bf4550..ad3fc9e01b 100755 --- a/modules/speicher_e3dc/main.sh +++ b/modules/speicher_e3dc/main.sh @@ -10,7 +10,7 @@ else MYLOGFILE="${RAMDISKDIR}/bat.log" fi -bash "$OPENWBBASEDIR/packages/legacy_run.sh" "speicher_e3dc.e3dc" "$e3dcip" "$e3dc2ip" "$e3dcextprod" "$pvwattmodul" >>"${MYLOGFILE}" 2>&1 +bash "$OPENWBBASEDIR/packages/legacy_run.sh" "modules.e3dc.device" "bat" "$e3dcip" "$e3dc2ip" "$e3dcextprod" "$pvwattmodul" "1">>"${MYLOGFILE}" 2>&1 ret=$? openwbDebugLog "${DMOD}" 2 "BAT RET: ${ret}" diff --git a/packages/modules/e3dc/bat.py b/packages/modules/e3dc/bat.py new file mode 100644 index 0000000000..048e5643d4 --- /dev/null +++ b/packages/modules/e3dc/bat.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Tuple +from modules.common import modbus +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.modbus import ModbusDataType, Endian +from modules.common.fault_state import ComponentInfo +from modules.common.store import get_bat_value_store +from modules.common.simcount._simcounter import SimCounter +from modules.e3dc.config import E3dcBatSetup + + +log = logging.getLogger(__name__) + + +def read_bat(client: modbus.ModbusTcpClient_) -> Tuple[int, int]: + # 40082 SoC + soc = client.read_holding_registers(40082, ModbusDataType.INT_16, unit=1) + # 40069 Speicherleistung + power = client.read_holding_registers(40069, ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) + return soc, power + + +class E3dcBat: + def __init__(self, + device_id: int, + component_config: E3dcBatSetup) -> None: + self.component_config = component_config + # bat + self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="speicher") + self.__store = get_bat_value_store(self.component_config.id) + self.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: modbus.ModbusTcpClient_) -> None: + + soc, power = read_bat(client) + imported, exported = self.sim_counter.sim_count(power) + bat_state = BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + self.__store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=E3dcBatSetup) diff --git a/packages/modules/e3dc/config.py b/packages/modules/e3dc/config.py new file mode 100644 index 0000000000..d6e9645fca --- /dev/null +++ b/packages/modules/e3dc/config.py @@ -0,0 +1,71 @@ +from modules.common.component_setup import ComponentSetup +from helpermodules.auto_str import auto_str + + +@auto_str +class E3dcConfiguration: + def __init__(self, address: str = None): + self.address = address + + +@auto_str +class E3dc: + def __init__(self, + name: str = "e3dc", + type: str = "e3dc", + id: int = 0, + configuration: E3dcConfiguration = None) -> None: + self.name = name + self.type = type + self.id = id + self.configuration = configuration or E3dcConfiguration() + + +@auto_str +class E3dcBatConfiguration: + def __init__(self): + pass + + +@auto_str +class E3dcBatSetup(ComponentSetup[E3dcBatConfiguration]): + def __init__(self, + name: str = "e3dc Speicher", + type: str = "bat", + id: int = 0, + configuration: E3dcBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration + or E3dcBatConfiguration()) + + +@auto_str +class E3dcCounterConfiguration: + def __init__(self): + pass + + +@auto_str +class E3dcCounterSetup(ComponentSetup[E3dcCounterConfiguration]): + def __init__(self, + name: str = "e3dc Zähler", + type: str = "counter", + id: int = 0, + configuration: E3dcCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or + E3dcCounterConfiguration()) + + +@auto_str +class E3dcInverterConfiguration: + def __init__(self, read_ext: int = 0): + self.read_ext = read_ext + + +@auto_str +class E3dcInverterSetup(ComponentSetup[E3dcInverterConfiguration]): + def __init__(self, + name: str = "E3dc Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: E3dcInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or E3dcInverterConfiguration()) diff --git a/packages/modules/e3dc/counter.py b/packages/modules/e3dc/counter.py new file mode 100644 index 0000000000..01c60ce4b1 --- /dev/null +++ b/packages/modules/e3dc/counter.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import logging +from typing import Tuple, List + +from modules.common import modbus +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo +from modules.common.simcount._simcounter import SimCounter +from modules.common.modbus import ModbusDataType, Endian +from modules.common.store import get_counter_value_store +from modules.e3dc.config import E3dcCounterSetup + +log = logging.getLogger(__name__) + + +def read_counter(client: modbus.ModbusTcpClient_) -> Tuple[int, List[int]]: + log.debug("Beginning EVU update") + power = client.read_holding_registers(40073, ModbusDataType.INT_32, wordorder=Endian.Little, unit=1) + # 40130,40131, 40132 je Phasenleistung in Watt + # max 7 Leistungsmesser verbaut ab 40105, typ 1 ist evu + # Modbus dokumentation Leistungsmesser von #0 bis #6 + # bei den meisten e3dc auf 40128 + meters = client.read_holding_registers(40104, [ModbusDataType.INT_16] * 28, unit=1) + log.debug("power: %d, meters: %s", power, meters) + powers = next(meters[i+1:i+4] for i in reversed(range(0, len(meters), 4)) if meters[i] == 1) + log.debug("powers %s", powers) + return power, powers + + +class E3dcCounter: + def __init__(self, + device_id: int, + component_config: E3dcCounterSetup) -> None: + self.component_config = component_config + self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="bezug") + self.__store = get_counter_value_store(self.component_config.id) + self.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: modbus.ModbusTcpClient_): + power, powers = read_counter(client) + imported, exported = self.sim_counter.sim_count(power) + counter_state = CounterState( + imported=imported, + exported=exported, + powers=powers, + power=power + ) + self.__store.set(counter_state) + log.debug("Update completed successfully") + + +component_descriptor = ComponentDescriptor(configuration_factory=E3dcCounterSetup) diff --git a/packages/modules/e3dc/device.py b/packages/modules/e3dc/device.py new file mode 100644 index 0000000000..8dce9b5f8f --- /dev/null +++ b/packages/modules/e3dc/device.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +import logging +from typing import List, Union, Iterable + +from helpermodules.cli import run_using_positional_cli_args +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, SingleComponentUpdateContext +from modules.common import modbus +from modules.e3dc.bat import E3dcBat, read_bat +from modules.e3dc.inverter import E3dcInverter, read_inverter +from modules.e3dc.counter import E3dcCounter +from modules.e3dc.config import E3dc, E3dcConfiguration +from modules.e3dc.config import E3dcBatSetup +from modules.e3dc.config import E3dcCounterSetup, E3dcCounterConfiguration +from modules.e3dc.config import E3dcInverterSetup +from modules.common.store.ramdisk import files +from modules.common.simcount import sim_count +from modules.common.store import get_inverter_value_store, get_bat_value_store +from modules.common.component_state import InverterState, BatState + + +log = logging.getLogger(__name__) + + +def create_device(device_config: E3dc): + def create_bat_component(component_config: E3dcBatSetup): + return E3dcBat(device_config.id, + component_config) + + def create_counter_component(component_config: E3dcCounterSetup): + return E3dcCounter(device_config.id, + component_config) + + def create_inverter_component(component_config: E3dcInverterSetup): + return E3dcInverter(device_config.id, + component_config) + + def update_components(components: Iterable[Union[E3dcBat, E3dcCounter, E3dcInverter]]): + with modbus.ModbusTcpClient_(device_config.configuration.address, 502) as client: + log.debug('reading: %s', device_config.configuration.address) + for component in components: + with SingleComponentUpdateContext(component.component_info): + component.update(client) + + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=update_components + ) + + +def run_device_legacy(device_config: E3dc, component_config: Union[E3dcBatSetup, E3dcCounterSetup, E3dcBatSetup]): + device = create_device(device_config) + device.add_component(component_config) + log.debug("E3dc Configuration: %s, Component Configuration: %s", device_config, component_config) + device.update() + + +def create_legacy_device_config(address: str, + num: int) -> E3dc: + device_config = E3dc(configuration=E3dcConfiguration(address=address), + id=num) + log.debug("Config: %s", device_config) + return device_config + + +def read_legacy_counter(address1: str, + num: int): + component_config = E3dcCounterSetup(configuration=E3dcCounterConfiguration()) + component_config.id = num + run_device_legacy(create_legacy_device_config(address1, + num), component_config) + + +def read_legacy_bat(address1: str, + address2: str, read_ext: int, + pv_module: str, + num: int) -> None: + # für openwbv19 können mit der bisherigen parametrisierung zwei ip_addressen + # ausgelesen werden + # ebenso wird bei Speicheraufruf beides (Speicher und PV ausgelesen + # in openwb v2.0 geht nur noch eine IP adresse und die Pv muss + # separate ausgelesen werden + addresses = [address for address in [address1, address2] if address != "none"] + log.debug('e3dc IP-Adresse1: %s', address1) + log.debug('e3dc IP-Adresse2: %s', address2) + log.debug('e3dc read_ext: %d', read_ext) + log.debug('e3dc pv_module: %s', pv_module) + log.debug('e3dc id: %d', num) + soc = 0 # type: Union[int, float] + power = 0 + pv_external = 0 + pv = 0 + pv_other = pv_module != "none" + for address in addresses: + log.debug("Ip: %s, read_external %d pv_other %s", address, read_ext, pv_other) + with modbus.ModbusTcpClient_(address, port=502) as client: + soc_tmp, power_tmp = read_bat(client) + soc += soc_tmp + power += power_tmp + pv_tmp, pv_external_tmp = read_inverter(client, read_ext) + pv += pv_tmp + pv_external += pv_external_tmp + soc /= len(addresses) + log.debug("Soc %d power %d pv %d pv_external %d", + soc, power, pv, pv_external) + counter_import, counter_export = sim_count(power, prefix="speicher") + get_bat_value_store(1).set(BatState(power=power, soc=soc, imported=counter_import, exported=counter_export)) + # pv_other sagt aus, ob WR definiert ist, und dessen PV Leistung auch gilt + # wenn 0 gilt nur PV und pv_external aus e3dc + pv_total = pv + pv_external + # Wenn wr1 nicht definiert ist, gilt nur die PV Leistung die hier im Modul ermittelt wurde + # als gesamte PV Leistung für wr1 + # Wenn wr1 definiert ist, gilt die bestehende PV Leistung aus Wr1 und das was hier im Modul ermittelt wurde + # als gesamte PV Leistung für wr1 + if pv_other: + pv_total = pv_total + files.pv[0].power.read() + log.debug("wr update pv_other %s pv_total %d", pv_other, pv_total) + _, exported_pv = sim_count(pv_total, prefix="pv") + get_inverter_value_store(num).set(InverterState(exported=exported_pv, power=pv_total)) + + +def main(argv: List[str]): + run_using_positional_cli_args( + {"bat": read_legacy_bat, "counter": read_legacy_counter}, argv + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=E3dc) diff --git a/packages/modules/e3dc/inverter.py b/packages/modules/e3dc/inverter.py new file mode 100644 index 0000000000..4364f1375a --- /dev/null +++ b/packages/modules/e3dc/inverter.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +from typing import Tuple +import logging + +from modules.common import modbus +from modules.common.component_state import InverterState +from modules.common.component_type import ComponentDescriptor +from modules.common.modbus import ModbusDataType, Endian +from modules.common.fault_state import ComponentInfo +from modules.common.store import get_inverter_value_store +from modules.common.simcount._simcounter import SimCounter +from modules.e3dc.config import E3dcInverterSetup + + +log = logging.getLogger(__name__) + + +def read_inverter(client: modbus.ModbusTcpClient_, read_ext) -> Tuple[int, int]: + # 40067 PV Leistung + pv = client.read_holding_registers(40067, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=1) * -1 + if read_ext == 1: + # 40075 externe PV Leistung + pv_external = client.read_holding_registers(40075, ModbusDataType.INT_32, + wordorder=Endian.Little, unit=1) + else: + pv_external = 0 + return pv, pv_external + + +class E3dcInverter: + def __init__(self, + device_id: int, + component_config: E3dcInverterSetup) -> None: + self.component_config = component_config + self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="pv") + self.__store = get_inverter_value_store(self.component_config.id) + self.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: modbus.ModbusTcpClient_) -> None: + + pv, pv_external = read_inverter(client, self.component_config.configuration.read_ext) + # pv_external - > pv Leistung + # die als externe Produktion an e3dc angeschlossen ist + # nur auslesen wenn als relevant parametrisiert + # (read_external = 1) , sonst doppelte Auslesung + # pv -> pv Leistung die direkt an e3dc angeschlossen ist + log.debug("read_ext %d", self.component_config.configuration.read_ext) + log.debug("pv %d pv_external %d", pv, pv_external) + # pv_other sagt aus, ob WR definiert ist, + # und dessen PV Leistung auch gilt + # wenn 0 gilt nur PV und pv_external aus e3dc + pv_total = pv + pv_external + # Wenn wr1 nicht definiert ist, + # gilt nur die PV Leistung die hier im Modul ermittelt wurde + # als gesamte PV Leistung für wr1 + # Im gegensatz zu v1.9 implementierung wird nicht mehr die PV + # leistung vom WR1 gelesen, da die durch v2.0 separat gehandelt wird + _, pv_exported = self.sim_counter.sim_count(pv_total) + inverter_state = InverterState( + power=pv_total, + exported=pv_exported + ) + self.__store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=E3dcInverterSetup) From dff82d50af3b0bf278d6e4c72ee7e56487221aa4 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Sat, 12 Nov 2022 13:04:41 +0100 Subject: [PATCH 032/201] show PV Charged energy in colors theme --- web/themes/colors/powerdata.js | 11 +- web/themes/colors/powergraph.js | 125 ++++--- web/themes/colors/processAllMqttMsg.js | 16 +- web/themes/colors/yieldmeter.js | 478 ++++++++++++++----------- 4 files changed, 356 insertions(+), 274 deletions(-) diff --git a/web/themes/colors/powerdata.js b/web/themes/colors/powerdata.js index 8db1c70507..ebab76fe52 100644 --- a/web/themes/colors/powerdata.js +++ b/web/themes/colors/powerdata.js @@ -46,7 +46,8 @@ class WbData { "charging": { name: "Laden", power: 0, energy: 0, color: "white" }, "devices": { name: "Geräte", power: 0, energy: 0, color: "white" }, "batIn": { name: "> Bat", power: 0, energy: 0, color: "white" }, - "house": { name: "Haus", power: 0, energy: 0, color: "white" } + "house": { name: "Haus", power: 0, energy: 0, color: "white" }, + "chargingPv": { name: "PVCharge", power: 0, energy: 0, color: "white"} }; this.historicSummary = { @@ -58,6 +59,7 @@ class WbData { "batIn": { name: "> Bat", power: 0, energy: 0, color: "white" }, "house": { name: "Haus", power: 0, energy: 0, color: "white" }, "devices": { name: "Geräte", power: 0, energy: 0, color: "white" }, + "chargingPv": {name: "PVCharge", power: 0, energy: 0, color: "white"} }; this.usageDetails = [this.usageSummary.evuOut]; @@ -82,6 +84,8 @@ class WbData { this.usageSummary.devices.color = 'var(--color-devices)'; this.usageSummary.batIn.color = 'var(--color-battery)'; this.usageSummary.house.color = 'var(--color-house)'; + this.usageSummary.chargingPv.color = 'var(--color-charging)'; + var i; for (i = 0; i < 8; i++) { this.chargePoint[i].color = 'var(--color-lp' + (i + 1) + ')'; @@ -102,6 +106,7 @@ class WbData { this.historicSummary.devices.color = 'var(--color-devices)'; this.historicSummary.batIn.color = 'var(--color-battery)'; this.historicSummary.house.color = 'var(--color-house)'; + this.historicSummary.chargingPv.color = 'var(--color-charging)'; evuCol = style.getPropertyValue('--evuCol'); xgridCol = style.getPropertyValue('--xgridCol'); tickCol = style.getPropertyValue('--tickCol'); @@ -338,7 +343,8 @@ class WbData { this.usageSummary.charging] .concat(this.shDevice.filter(row => (row.configured && row.showInGraph))) .concat(this.consumer.filter(row => (row.configured))) - .concat([this.usageSummary.batIn, this.usageSummary.house]); + .concat([this.usageSummary.batIn, this.usageSummary.house]) + .concat([this.usageSummary.chargingPv]); } updateConsumerSummary(cat) { @@ -551,6 +557,7 @@ function shiftRight() { wbdata.persistGraphPreferences(); d3.select("button#graphLeftButton").classed("disabled", false) d3.select("button#graphRightButton").classed("disabled", true) + yieldMeter.update() } else { // currently looking at a previous day wbdata.graphDate.setTime(wbdata.graphDate.getTime() + 86400000); const nd = wbdata.graphDate; diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 7ed072be7b..2e1999dc13 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -70,7 +70,6 @@ class PowerGraph { .on("click", changeStack) d3.select("button#gridChangeButton") .on("click", toggleGrid) - } activateLive() { @@ -146,6 +145,7 @@ class PowerGraph { } catch (err) { //on initial run of activate, subscribeDayGraph is not yet initialized. // the error can be ignored + console.error("subscribeMonthGraph failed") } this.updateHeading(); } @@ -251,7 +251,7 @@ class PowerGraph { if (segment[0] == "") { segment = []; } - const serialNo = topic.substring(28, topic.length); + const serialNo = topic.substring(29, topic.length); if (serialNo != "") { if (typeof (this.staging[+serialNo - 1]) === 'undefined') { this.staging[+serialNo - 1] = segment; @@ -261,13 +261,23 @@ class PowerGraph { if (this.initCounter == 12) {// Initialization complete unsubscribeMonthGraph(); this.initCounter = 0; - this.staging.map(segment => - segment.map(line => this.rawData.push(line)) - ) + this.staging.map((segment, i) => { + if (i == 3) { + segment.map((line, i) => { + if (line.length > 1) { this.rawData.push(line) } + }) + } + }) this.rawData.map((line, i, a) => { if (i > 0) { - const values = this.extractMonthValues(line, a[i - 1]); - this.graphData.push(values); + if (line != "0" && line != "") { + const values = this.extractMonthValues(line, a[i - 1]); + if ((values.date.getFullYear() == wbdata.graphMonth.year) + && ((values.date.getMonth() == wbdata.graphMonth.month) + || ((values.date.getMonth() == (wbdata.graphMonth.month +1)) && (values.date.getDate == 1)) + ) + ) { this.graphData.push(values); } + } } else { // const values = this.extractValues(line, []); } @@ -293,7 +303,7 @@ class PowerGraph { let deviceIndex = (wbdata.graphMode == 'day') ? 26 : 19; for (var i = 0; i < 9; i++) { deviceEnergy = (endValues[deviceIndex + i] - startValues[deviceIndex + i]) / 1000; - if (deviceEnergy < 0) {deviceEnergy = 0} + if (deviceEnergy < 0) { deviceEnergy = 0 } deviceEnergySum = deviceEnergySum + deviceEnergy wbdata.historicSummary['sh' + i].energy = deviceEnergy } @@ -303,8 +313,13 @@ class PowerGraph { wbdata.historicSummary.batIn.energy = (endValues[8] - startValues[8]) / 1000; wbdata.historicSummary.house.energy = wbdata.historicSummary.evuIn.energy + wbdata.historicSummary.pv.energy + wbdata.historicSummary.batOut.energy - wbdata.historicSummary.evuOut.energy - wbdata.historicSummary.batIn.energy - wbdata.historicSummary.charging.energy - wbdata.historicSummary.devices.energy; + + let pvCharged = this.graphData.reduce((prev, cur) => { + return prev + (cur.chargingPv / 12); + }, 0) + wbdata.historicSummary.chargingPv.energy = pvCharged / 1000; + wbdata.usageSummary.chargingPv.energy = pvCharged / 1000; } - // console.log (wbdata.historicSummary) } extractLiveValues(payload) { const elements = payload.split(","); @@ -397,6 +412,8 @@ class PowerGraph { for (i = 3; i < 8; i++) { values["lp" + i] = this.calcValue(12 + i, elements, oldElements); } + values.lpSum = this.calcValue(7, elements, oldElements); + values.lpSumPv = this.calcValue(29, elements, oldElements); values.soc1 = +elements[21]; values.soc2 = +elements[22]; // smart home @@ -421,49 +438,57 @@ class PowerGraph { if (values.housePower < 0) { values.housePower = 0; }; values.selfUsage = values.solarPower - values.gridPush; if (values.selfUsage < 0) { values.selfUsage = 0; }; + if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { + values.chargingPv = values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) + } else { + values.chargingPv = 0; + } return values; } extractMonthValues(payload, oldPayload) { - const elements = payload.split(","); - const oldElements = oldPayload.split(","); - var values = {}; - values.date = new Date(d3.timeParse("%Y%m%d%H%M")(oldElements[0] + '1200')); - // evu - values.gridPull = this.calcMonthlyValue(1, elements, oldElements); - values.gridPush = this.calcMonthlyValue(2, elements, oldElements); - // pv - values.solarPower = this.calcMonthlyValue(3, elements, oldElements); - values.inverter = 0; - // charge points - values.charging = this.calcMonthlyValue(7, elements, oldElements); - var i; - for (i = 0; i < 3; i++) { - values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); - } - for (i = 3; i < 8; i++) { - values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements, oldElements); - } - values.soc1 = +elements[21]; - values.soc2 = +elements[22]; - // smart home - for (i = 0; i < 10; i++) { - values["sh" + i] = this.calcMonthlyValue(19 + i, elements, oldElements); - } - //consumers - values.co0 = this.calcMonthlyValue(10, elements, oldElements); - values.co1 = this.calcMonthlyValue(12, elements, oldElements); - //battery - values.batIn = this.calcMonthlyValue(17, elements, oldElements); - values.batOut = this.calcMonthlyValue(18, elements, oldElements); - values.batterySoc = +elements[20]; - // calculated values - values.housePower = values.gridPull + values.solarPower + values.batOut - - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 - - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8 - values.sh9; if (values.housePower < 0) { values.housePower = 0; }; - values.selfUsage = values.solarPower - values.gridPush; - if (values.selfUsage < 0) { values.selfUsage = 0; }; - return values; + if (payload != "0") { + const elements = payload.split(","); + const oldElements = oldPayload.split(","); + var values = {}; + values.date = new Date(d3.timeParse("%Y%m%d%H%M")(oldElements[0] + '1200')); + // evu + values.gridPull = this.calcMonthlyValue(1, elements, oldElements); + values.gridPush = this.calcMonthlyValue(2, elements, oldElements); + // pv + values.solarPower = this.calcMonthlyValue(3, elements, oldElements); + values.inverter = 0; + // charge points + values.charging = this.calcMonthlyValue(7, elements, oldElements); + values.chargingPv = this.calcMonthlyValue(29, elements, oldElements); + var i; + for (i = 0; i < 3; i++) { + values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); + } + for (i = 3; i < 8; i++) { + values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements, oldElements); + } + values.soc1 = +elements[21]; + values.soc2 = +elements[22]; + // smart home + for (i = 0; i < 10; i++) { + values["sh" + i] = this.calcMonthlyValue(19 + i, elements, oldElements); + } + //consumers + values.co0 = this.calcMonthlyValue(10, elements, oldElements); + values.co1 = this.calcMonthlyValue(12, elements, oldElements); + //battery + values.batIn = this.calcMonthlyValue(17, elements, oldElements); + values.batOut = this.calcMonthlyValue(18, elements, oldElements); + values.batterySoc = +elements[20]; + // calculated values + values.housePower = values.gridPull + values.solarPower + values.batOut + - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 + - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8 - values.sh9; if (values.housePower < 0) { values.housePower = 0; }; + values.selfUsage = values.solarPower - values.gridPush; + if (values.selfUsage < 0) { values.selfUsage = 0; }; + return values; + } else return {} } reset() { this.resetLiveGraph(); @@ -477,6 +502,8 @@ class PowerGraph { this.initialGraphData = []; this.graphData = []; this.graphRefreshCounter = 0; + wbdata.historicSummary.chargingPv.energy = 0; + wbdata.usageSummary.chargingPv.energy = 0; } resetDayGraph() { @@ -495,8 +522,6 @@ class PowerGraph { this.graphData = []; } - - calcValue(i, array, oldArray) { var val = (array[i] - oldArray[i]) * 12; if (val < 0 || val > 150000) { diff --git a/web/themes/colors/processAllMqttMsg.js b/web/themes/colors/processAllMqttMsg.js index 35b5307198..d2da5e4869 100644 --- a/web/themes/colors/processAllMqttMsg.js +++ b/web/themes/colors/processAllMqttMsg.js @@ -37,6 +37,7 @@ function getIndex(topic) { function handlevar(mqttmsg, mqttpayload) { // receives all messages and calls respective function to process them + if (mqttmsg.match(/^openwb\/graph\//i)) { processGraphMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/evu\//i)) { processEvuMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/global\/awattar\//i)) { processETProviderMessages(mqttmsg, mqttpayload); } @@ -53,7 +54,7 @@ function handlevar(mqttmsg, mqttpayload) { else if (mqttmsg.match(/^openwb\/SmartHome\/Status\//i)) { processSmartHomeDevicesStatusMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/config\/get\/sofort\/lp\//i)) { processSofortConfigMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/config\/get\/pv\//i)) { processPvConfigMessages(mqttmsg, mqttpayload); } -} // end handlevar + } // end handlevar function processETProviderMessages(mqttmsg, mqttpayload) { // processes mqttmsg for topic openWB/global @@ -647,7 +648,7 @@ function processSystemMessages(mqttmsg, mqttpayload) { else if (mqttmsg.match(/^openwb\/system\/daygraphdata[1-9][0-9]*$/i)) { powerGraph.updateDay(mqttmsg, mqttpayload); } - else if (mqttmsg.match(/^openwb\/system\/monthgraphdata[1-9][0-9]*$/i)) { + else if (mqttmsg.match(/^openwb\/system\/monthgraphdatan[1-9][0-9]*$/i)) { powerGraph.updateMonth(mqttmsg, mqttpayload); } } @@ -1288,19 +1289,22 @@ function unsubscribeDayGraph() { } function subscribeMonthGraph(date) { - // var today = new Date(); var mm = String(date.month + 1).padStart(2, '0'); //January is 0! var yyyy = date.year; graphdate = yyyy + mm; for (var segment = 1; segment < 13; segment++) { - var topic = "openWB/system/MonthGraphData" + segment; + var topic = "openWB/system/MonthGraphDatan" + segment; client.subscribe(topic, { qos: 0 }); } - publish(graphdate, "openWB/set/graph/RequestMonthGraph"); + publish(graphdate, "openWB/set/graph/RequestMonthGraphv1"); } function unsubscribeMonthGraph() { - publish("0", "openWB/set/graph/RequestMonthGraph"); + for (var segment = 1; segment < 13; segment++) { + var topic = "openWB/system/MonthGraphDatan" + segment; + client.unsubscribe(topic); + } + publish("0", "openWB/set/graph/RequestMonthGraphv1"); } function makeInt(message) { diff --git a/web/themes/colors/yieldmeter.js b/web/themes/colors/yieldmeter.js index 4550220818..1a0cd777d9 100644 --- a/web/themes/colors/yieldmeter.js +++ b/web/themes/colors/yieldmeter.js @@ -5,222 +5,268 @@ */ class YieldMeter { - bardata; - xScale; - yScale; - svg; - - constructor() { - this.width = 500; - this.height = 500; - this.margin = { - top: 25, bottom: 30, left: 25, right: 0 - }; - this.labelfontsize = 16; - this.axisFontSize = 12; - this.textLength = 12; - } - - // to be called when the document is loaded - init() { - const figure = d3.select("figure#energymeter"); - this.svg = figure.append("svg") - .attr("viewBox", `0 0 500 500`); - const style = getComputedStyle(document.body); - this.houseColor = 'var(--color-house)'; - this.pvColor = 'var(--color-pv)'; - this.exportColor = 'var(--color-export)'; - this.evuColor = 'var(--color-evu)'; - this.bgColor = 'var(--color-bg)'; - this.chargeColor = 'var(--color-charging)'; - this.axisColor = 'var(--color-axis)'; - this.gridColor = 'var(--color-grid)'; - d3.select("button#energyLeftButton") - .on("click", shiftLeft) - d3.select("button#energyRightButton") - .on("click", shiftRight) - d3.select("button#calendarButton") - .on("click", toggleMonthView) - } - - // to be called when values have changed - update() { - switch (wbdata.graphMode) { - case 'live': - this.plotdata = Object.values(wbdata.sourceSummary) - .filter((row) => (row.energy > 0)) - .concat(wbdata.usageDetails - .filter((row) => (row.energy > 0))); - break; - case 'day': - if (wbdata.showTodayGraph) { - this.plotdata = Object.values(wbdata.sourceSummary) - .filter((row) => (row.energy > 0)) - .concat(wbdata.usageDetails - .filter((row) => (row.energy > 0))); - } else { - this.plotdata = Object.values(wbdata.historicSummary) - .filter((row) => (row.energy > 0)); - } - break; - case 'month': - this.plotdata = Object.values(wbdata.historicSummary) - .filter((row) => (row.energy > 0)); - break; - default: break; - } - this.adjustLabelSize() - const svg = this.createOrUpdateSvg(); - this.drawChart(svg); - this.updateHeading(); - }; - - createOrUpdateSvg() { - this.svg.selectAll("*").remove(); - const g = this.svg.append("g") - .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); - this.xScale = d3.scaleBand() - .range([0, this.width - this.margin.left - this.margin.right]) - .padding(0.4); - this.yScale = d3.scaleLinear() - .range([this.height - this.margin.bottom - this.margin.top, 0]); - return g; - } - - drawChart(svg) { - const ymax = d3.max(this.plotdata, (d) => d.energy); - this.xScale.domain(this.plotdata.map((d) => d.name)); - this.yScale.domain([0, Math.ceil(ymax)]); - const bargroups = svg - .selectAll(".bar") - .data(this.plotdata) - .enter() - .append("g"); - bargroups - .append("rect") - .attr("class", "bar") - .attr("x", (d) => this.xScale(d.name)) - .attr("y", (d) => this.yScale(d.energy)) - .attr("width", this.xScale.bandwidth()) - .attr("height", (d) => this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom) - .attr("fill", (d) => d.color); - - const yAxisGenerator = d3.axisLeft(this.yScale) - .tickFormat(function (d) { - return ((d > 0) ? d : ""); - }) - .ticks(8) - .tickSizeInner(-this.width); - - const yAxis = svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(0," + 0 + ")") - .call(yAxisGenerator); - - yAxis.append("text") - .attr("y", 6) - .attr("dy", "0.71em") - .attr("text-anchor", "end") - .text("energy"); - - yAxis.selectAll(".tick").attr("font-size", this.axisFontSize); - if (wbdata.showGrid) { - yAxis.selectAll(".tick line") - .attr("stroke", this.gridColor) - .attr("stroke-width", "0.5"); - } else { - yAxis.selectAll(".tick line").attr("stroke", this.bgColor); - } - yAxis.select(".domain") - .attr("stroke", this.bgcolor); - - svg.append("text") - .attr("x", -this.margin.left) - .attr("y", -15) - .style("fill", this.axisColor) - .attr("font-size", this.axisFontSize) - .text("kWh") - ; - const labels = svg.selectAll(".label") - .data(this.plotdata) - .enter() - .append("g"); - labels - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", (d) => this.yScale(d.energy) - 10) - .attr("font-size", this.labelfontsize) - .attr("text-anchor", "middle") - .attr("fill", (d) => d.color) - .text((d) => (formatWattH(d.energy * 1000))); - - const categories = svg.selectAll(".category") - .data(this.plotdata) - .enter() - .append("g"); - labels - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", this.height - this.margin.bottom - 5) - .attr("font-size", this.labelfontsize) - .attr("text-anchor", "middle") - .attr("fill", (d) => d.color) - .text((d) => (this.truncateCategory(d.name))); - } - - updateHeading() { - var heading = "Energie "; - - switch (wbdata.graphMode) { - case 'live': - heading = heading + " heute"; - break; - case 'day': - if (wbdata.showTodayGraph) { - heading = heading + " heute"; - } else { - heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; - } - break; - case 'month': - heading = "Monatswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); - break; - default: break; - } - d3.select("h3#energyheading").text(heading); - } - - adjustLabelSize() { - let xCount = this.plotdata.length - if (xCount <= 5) { - this.maxTextLength = 12; - this.labelfontsize = 16 - } else if (xCount == 6) { - this.maxTextLength = 11; - this.labelfontsize = 14 - } else if (xCount > 6 && xCount <= 8) { - this.maxTextLength = 8; - this.labelfontsize = 13 - } else if (xCount == 9) { - this.maxTextLength = 8; - this.labelfontsize = 11; - } else if (xCount == 10) { - this.maxTextLength = 7; - this.labelfontsize = 10; - } - else { - this.maxTextLength = 6; - this.labelfontsize = 9 - } - } - - truncateCategory(name) { - if (name.length > this.maxTextLength) { - return name.substr(0, this.maxTextLength) + "." - } else { - return name - } - } + bardata; + xScale; + yScale; + svg; + + constructor() { + this.width = 500; + this.height = 500; + this.margin = { + top: 25, bottom: 30, left: 25, right: 0 + }; + this.labelfontsize = 16; + this.axisFontSize = 12; + this.textLength = 12; + } + + // to be called when the document is loaded + init() { + const figure = d3.select("figure#energymeter"); + this.svg = figure.append("svg") + .attr("viewBox", `0 0 500 500`); + const style = getComputedStyle(document.body); + this.houseColor = 'var(--color-house)'; + this.pvColor = 'var(--color-pv)'; + this.exportColor = 'var(--color-export)'; + this.evuColor = 'var(--color-evu)'; + this.bgColor = 'var(--color-bg)'; + this.chargeColor = 'var(--color-charging)'; + this.axisColor = 'var(--color-axis)'; + this.gridColor = 'var(--color-grid)'; + d3.select("button#energyLeftButton") + .on("click", shiftLeft) + d3.select("button#energyRightButton") + .on("click", shiftRight) + d3.select("button#calendarButton") + .on("click", toggleMonthView) + } + + // to be called when values have changed + update() { + switch (wbdata.graphMode) { + case 'live': + this.plotdata = Object.values(wbdata.sourceSummary) + .filter((row) => (row.energy > 0)) + .concat(wbdata.usageDetails + .filter((row) => (row.energy > 0))); + break; + case 'day': + if (wbdata.showTodayGraph) { + this.plotdata = Object.values(wbdata.sourceSummary) + .filter((row) => (row.energy > 0)) + .concat(wbdata.usageDetails + .filter((row) => (row.energy > 0))); + } else { + this.plotdata = Object.values(wbdata.historicSummary) + .filter((row) => (row.energy > 0)); + } + break; + case 'month': + this.plotdata = Object.values(wbdata.historicSummary) + .filter((row) => (row.energy > 0)); + break; + default: break; + } + this.adjustLabelSize() + const svg = this.createOrUpdateSvg(); + this.drawChart(svg); + this.updateHeading(); + }; + + createOrUpdateSvg() { + this.svg.selectAll("*").remove(); + const g = this.svg.append("g") + .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); + this.xScale = d3.scaleBand() + .range([0, this.width - this.margin.left - this.margin.right]) + .padding(0.4); + this.yScale = d3.scaleLinear() + .range([this.height - this.margin.bottom - this.margin.top, 10]); + return g; + } + + drawChart(svg) { + let pvcdata = this.plotdata.filter(d => (d.name == "PVCharge")) + let chargedata = this.plotdata.filter(d => d.name == "Laden") + this.plotdata = this.plotdata.filter(d => (d.name != "PVCharge")) + const ymax = d3.max(this.plotdata, (d) => d.energy); + this.xScale.domain(this.plotdata.map((d) => d.name)); + this.yScale.domain([0, Math.ceil(ymax)]); + + // Draw the bars + const bargroups = svg + .selectAll(".bar") + .data(this.plotdata) + .enter() + .append("g"); + bargroups + .append("rect") + .attr("class", "bar") + .attr("x", (d) => this.xScale(d.name)) + .attr("y", (d) => this.yScale(d.energy)) + .attr("width", this.xScale.bandwidth()) + .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) + .attr("fill", (d) => d.color); + + // Display the PV Charging inner bar + if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { + const pvcBargroup = svg + .selectAll(".pvcBar") + .data(pvcdata) + .enter() + .append("g") + pvcBargroup + .append("rect") + .attr("class", "bar") + .attr("x", (d) => this.xScale("Laden") + this.xScale.bandwidth() / 6) + .attr("y", (d) => this.yScale(d.energy)) + .attr("width", this.xScale.bandwidth() * 2 / 3) + .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) + .attr("fill", this.pvColor) + .attr("fill-opacity", "66%"); + } + const yAxisGenerator = d3.axisLeft(this.yScale) + .tickFormat(function (d) { + return ((d > 0) ? d : ""); + }) + .ticks(8) + .tickSizeInner(-this.width); + + const yAxis = svg.append("g") + .attr("class", "axis") + .attr("transform", "translate(0," + 0 + ")") + .call(yAxisGenerator); + + yAxis.append("text") + .attr("y", 6) + .attr("dy", "0.71em") + .attr("text-anchor", "end") + .text("energy"); + + yAxis.selectAll(".tick").attr("font-size", this.axisFontSize); + if (wbdata.showGrid) { + yAxis.selectAll(".tick line") + .attr("stroke", this.gridColor) + .attr("stroke-width", "0.5"); + } else { + yAxis.selectAll(".tick line").attr("stroke", this.bgColor); + } + yAxis.select(".domain") + .attr("stroke", this.bgcolor); + + svg.append("text") + .attr("x", -this.margin.left) + .attr("y", -15) + .style("fill", this.axisColor) + .attr("font-size", this.axisFontSize) + .text("kWh") + ; + + // add value labels to the bars + const labels = svg.selectAll(".label") + .data(this.plotdata) + .enter() + .append("g"); + labels + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", (d) => { + if (d.name == "Laden" && pvcdata.length > 0) { + return this.yScale(d.energy) - 25 + } else { + return this.yScale(d.energy) - 10 + } + }) + .attr("font-size", this.labelfontsize) + .attr("text-anchor", "middle") + .attr("fill", (d) => d.color) + .text((d) => (formatWattH(d.energy * 1000))); + + // add a PV percentage tag to the charging bar + if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { + let pvRatio = Math.round (pvcdata[0].energy / chargedata[0].energy * 100) + + const pvtags = svg.selectAll(".pvtag") + .data(chargedata) + .enter() + .append("g"); + pvtags + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", (d) => this.yScale(d.energy) - 10) + .attr("font-size", this.labelfontsize - 2) + .attr("text-anchor", "middle") + .attr("fill", (d) => this.pvColor) + .text((d) => ("(PV: " + pvRatio.toLocaleString(undefined) + " %)")); + } + + // Add category labels + labels + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", this.height - this.margin.bottom - 5) + .attr("font-size", this.labelfontsize) + .attr("text-anchor", "middle") + .attr("fill", (d) => d.color) + .text((d) => (this.truncateCategory(d.name))); + + } + + updateHeading() { + var heading = "Energie "; + + switch (wbdata.graphMode) { + case 'live': + heading = heading + " heute"; + break; + case 'day': + if (wbdata.showTodayGraph) { + heading = heading + " heute"; + } else { + heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; + } + break; + case 'month': + heading = "Monatswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); + break; + default: break; + } + d3.select("h3#energyheading").text(heading); + } + + adjustLabelSize() { + let xCount = this.plotdata.length + if (xCount <= 5) { + this.maxTextLength = 12; + this.labelfontsize = 16 + } else if (xCount == 6) { + this.maxTextLength = 11; + this.labelfontsize = 14 + } else if (xCount > 6 && xCount <= 8) { + this.maxTextLength = 8; + this.labelfontsize = 13 + } else if (xCount == 9) { + this.maxTextLength = 8; + this.labelfontsize = 11; + } else if (xCount == 10) { + this.maxTextLength = 7; + this.labelfontsize = 10; + } + else { + this.maxTextLength = 6; + this.labelfontsize = 9 + } + } + + truncateCategory(name) { + if (name.length > this.maxTextLength) { + return name.substr(0, this.maxTextLength) + "." + } else { + return name + } + } } From 867aecfed0a91808088db9f98672634e81cf5d52 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Sat, 12 Nov 2022 13:13:11 +0100 Subject: [PATCH 033/201] change indentation from spaces to tabs --- web/themes/colors/powergraph.js | 1796 +++++++++++++++---------------- 1 file changed, 898 insertions(+), 898 deletions(-) diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 2e1999dc13..445bcda1e1 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -1,906 +1,906 @@ class PowerGraph { - svg; - xScale; - - constructor() { - this.graphData = []; - this.initCounter = 0; - this.staging = []; - this.rawData = []; - this.graphdata = [] - this.initialGraphData = []; - this.initialized = false; - this.colors = []; - this.gridColors = {}; - this.bgcolor = ""; - this.axiscolor = ""; - this.chargeColor = ""; - this.lp1color = ""; - this.lp2color = ""; - this.batteryColor = ""; - this.batSocColor = ""; - this.graphRefreshCounter = 0; - this.width = 500; - this.height = 500; - this.margin = { top: 10, right: 20, bottom: 10, left: 25 }; - this.liveGraphMinutes = 0; - wbdata.usageStackOrder = 2; - } - - init() { - var style = getComputedStyle(document.body); - this.colors.housePower = 'var(--color-house)'; - this.colors.batIn = 'var(--color-battery)'; - this.colors.inverter = 'var(--color-pv)'; - this.gridColors[0] = 'var(--color-battery)'; - this.colors.batOut = 'var(--color-battery)'; - this.gridColors[1] = 'var(--color-pv)'; - this.colors.selfUsage = 'var(--color-pv)'; - this.gridColors[2] = 'var(--color-export)'; - this.colors.gridPush = 'var(--color-export)'; - this.gridColors[3] = 'var(--color-evu)'; - this.colors.gridPull = 'var(--color-evu)'; - this.bgcolor = 'var(--color-bg)'; - this.chargeColor = 'var(--color-charging)'; - this.axiscolor = 'var(--color-axis)'; - this.gridcolor = 'var(--color-grid)'; - this.lp1color = 'var(--color-lp1)'; - this.lp2color = 'var(--color-lp2)'; - this.batteryColor = 'var(--color-battery)'; - this.batSocColor = 'var(--color-title)'; - var i; - for (i = 0; i < 8; i++) { - this.colors["lp" + i] = wbdata.chargePoint[i].color; - } - for (i = 0; i < 8; i++) { - this.colors["sh" + i] = wbdata.shDevice[i].color; - } - this.colors.co0 = 'var(--color-co1)'; - this.colors.co1 = 'var(--color-co2)'; - - var figure = d3.select("figure#powergraph"); - this.svg = figure.append("svg") - .attr("viewBox", `0 0 500 500`); - - d3.select("button#graphLeftButton") - .on("click", shiftLeft) - d3.select("button#graphRightButton") - .on("click", shiftRight) - d3.select("button#graphChangeButton") - .on("click", changeStack) - d3.select("button#gridChangeButton") - .on("click", toggleGrid) - } - - activateLive() { - try { - this.resetLiveGraph(); - subscribeMqttGraphSegments(); - subscribeGraphUpdates(); - } catch (err) { - // on initial invocation this method is not existing - } - this.updateHeading(); - } - - deactivateLive() { - try { - unsubscribeMqttGraphSegments(); - unsubscribeGraphUpdates(); - } catch (err) { - // on initial run this method is not existing - } - } - - activateDay() { - if (wbdata.graphMode == 'day') { - if (wbdata.showTodayGraph) { - wbdata.graphDate = new Date(); // ensure we update todays date if day changes during display - } - this.resetDayGraph(); - try { - subscribeDayGraph(wbdata.graphDate); - } catch (err) { - //on initial run of activate, subscribeDayGraph is not yet initialized. - // the error can be ignored - } - this.updateHeading(); - } - } - - updateHeading() { - var heading = "Leistung / Ladestand "; - switch (wbdata.graphMode) { - case 'live': - heading = heading + this.liveGraphMinutes + " min"; - break; - case 'day': - const today = new Date(); - if (today.getDate() == wbdata.graphDate.getDate() && today.getMonth() == wbdata.graphDate.getMonth() && today.getFullYear() == wbdata.graphDate.getFullYear()) { - heading = heading + "heute"; - } else { - heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; - } - break; - case 'month': - heading = "Tageswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); - break; - default: break; - } - d3.select("h3#graphheading").text(heading); - } - - deactivateDay() { - try { - unsubscribeDayGraph(); - } catch (err) { - // ignore error - } - } - - activateMonth() { - this.resetMonthGraph(); - try { - subscribeMonthGraph(wbdata.graphMonth); - } catch (err) { - //on initial run of activate, subscribeDayGraph is not yet initialized. - // the error can be ignored - console.error("subscribeMonthGraph failed") - } - this.updateHeading(); - } - - deactivateMonth() { - try { - unsubscribeMonthGraph(); - } catch (err) { - // ignore error - } - } - - updateLive(topic, payload) { - if (wbdata.graphMode == 'live') { // only update if live graph is active - if (this.initialized) { // steady state - if (topic === "openWB/graph/lastlivevalues") { - const values = this.extractLiveValues(payload.toString()); - this.graphRefreshCounter++; - this.graphData.push(values); - this.updateGraph(); - if (this.graphRefreshCounter > 60) { - this.resetLiveGraph(); - subscribeMqttGraphSegments(); - } - } - } else { // init phase - const t = topic; - if (t.substring(t.length - 13, t.length) === "alllivevalues") { - // init message - const serialNo = t.substring(13, t.length - 13); - var bulkdata = payload.toString().split("\n"); - if (bulkdata.length <= 1) { - bulkdata = []; - } - if (serialNo != "") { - if (typeof (this.initialGraphData[+serialNo - 1]) === 'undefined') { - this.initialGraphData[+serialNo - 1] = bulkdata; - this.initCounter++; - } - } - if (this.initCounter == 16) {// Initialization complete - this.initialized = true; - this.initialGraphData.map(bulkdata => { - bulkdata.map((line) => { - const values = this.extractLiveValues(line); - this.graphData.push(values); - }); - }); - const startTime = this.graphData[0].date; - const endTime = this.graphData[this.graphData.length - 1].date; - this.liveGraphMinutes = Math.round((endTime - startTime) / 60000); - this.updateHeading(); - this.updateGraph(); - unsubscribeMqttGraphSegments(); - } - } - } - } - } - - updateDay(topic, payload) { - var segment; - if (payload == 'empty') { - segment = []; - } else { - segment = payload.toString().split("\n"); - if (segment[0] == "") { - segment = []; - } - const serialNo = topic.substring(26, topic.length); - if (serialNo != "") { - if (typeof (this.staging[+serialNo - 1]) === 'undefined') { - this.staging[+serialNo - 1] = segment; - this.initCounter++; - } - } - } - if (this.initCounter == 12) {// Initialization complete - unsubscribeDayGraph(); - - this.initCounter = 0; - this.staging.map(segment => - segment.map(line => this.rawData.push(line)) - ) - this.rawData.map((line, i, a) => { - if (i > 0) { - const values = this.extractDayValues(line, a[i - 1]); - this.graphData.push(values); - } else { - // const values = this.extractValues(line, []); - } - }); - this.updateGraph(); - this.updateEnergyValues(); - wbdata.dayGraphUpdated(); - setTimeout(() => this.activateDay(), 300000) - } - } - - updateMonth(topic, payload) { - if (payload != 'empty') { - var segment = payload.toString().split("\n"); - if (segment[0] == "") { - segment = []; - } - const serialNo = topic.substring(29, topic.length); - if (serialNo != "") { - if (typeof (this.staging[+serialNo - 1]) === 'undefined') { - this.staging[+serialNo - 1] = segment; - this.initCounter++; - } - } - if (this.initCounter == 12) {// Initialization complete - unsubscribeMonthGraph(); - this.initCounter = 0; - this.staging.map((segment, i) => { - if (i == 3) { - segment.map((line, i) => { - if (line.length > 1) { this.rawData.push(line) } - }) - } - }) - this.rawData.map((line, i, a) => { - if (i > 0) { - if (line != "0" && line != "") { - const values = this.extractMonthValues(line, a[i - 1]); - if ((values.date.getFullYear() == wbdata.graphMonth.year) - && ((values.date.getMonth() == wbdata.graphMonth.month) - || ((values.date.getMonth() == (wbdata.graphMonth.month +1)) && (values.date.getDate == 1)) - ) - ) { this.graphData.push(values); } - } - } else { - // const values = this.extractValues(line, []); - } - }); - this.updateGraph(); - this.updateEnergyValues(); - wbdata.monthGraphUpdated(); - // setTimeout(() => this.activateDay(), 300000) - } - } - } - updateEnergyValues() { - if (this.rawData.length) { - const startValues = this.rawData[0].split(','); - const endValues = this.rawData[this.rawData.length - 1].split(','); - wbdata.historicSummary.pv.energy = (endValues[3] - startValues[3]) / 1000; - wbdata.historicSummary.evuIn.energy = (endValues[1] - startValues[1]) / 1000; - wbdata.historicSummary.batOut.energy = (endValues[9] - startValues[9]) / 1000; - wbdata.historicSummary.evuOut.energy = (endValues[2] - startValues[2]) / 1000; - wbdata.historicSummary.charging.energy = (endValues[7] - startValues[7]) / 1000; - var deviceEnergySum = 0; - var deviceEnergy = 0; - let deviceIndex = (wbdata.graphMode == 'day') ? 26 : 19; - for (var i = 0; i < 9; i++) { - deviceEnergy = (endValues[deviceIndex + i] - startValues[deviceIndex + i]) / 1000; - if (deviceEnergy < 0) { deviceEnergy = 0 } - deviceEnergySum = deviceEnergySum + deviceEnergy - wbdata.historicSummary['sh' + i].energy = deviceEnergy - } - deviceEnergySum = deviceEnergySum + (endValues[10] - startValues[10]) / 1000; - deviceEnergySum = deviceEnergySum + (endValues[12] - startValues[12]) / 1000; - wbdata.historicSummary.devices.energy = deviceEnergySum; - wbdata.historicSummary.batIn.energy = (endValues[8] - startValues[8]) / 1000; - wbdata.historicSummary.house.energy = wbdata.historicSummary.evuIn.energy + wbdata.historicSummary.pv.energy + wbdata.historicSummary.batOut.energy - - wbdata.historicSummary.evuOut.energy - wbdata.historicSummary.batIn.energy - wbdata.historicSummary.charging.energy - wbdata.historicSummary.devices.energy; - - let pvCharged = this.graphData.reduce((prev, cur) => { - return prev + (cur.chargingPv / 12); - }, 0) - wbdata.historicSummary.chargingPv.energy = pvCharged / 1000; - wbdata.usageSummary.chargingPv.energy = pvCharged / 1000; - } - } - extractLiveValues(payload) { - const elements = payload.split(","); - const now = new Date(Date.now()); - const mSecondsPerDay = 86400000 // milliseconds in a day - var values = {}; - values.date = new Date(d3.timeParse("%H:%M:%S")(elements[0])); - values.date.setDate(now.getDate()) - values.date.setMonth(now.getMonth()) - values.date.setFullYear(now.getFullYear()) - if (values.date.getHours() > now.getHours()) { // this is an entry from yesterday - values.date = new Date(values.date.getTime() - mSecondsPerDay) // change date to yesterday - } - // evu - if (+elements[1] > 0) { - values.gridPull = +elements[1]; - values.gridPush = 0; - } else { - values.gridPull = 0; - values.gridPush = -elements[1]; - } - // pv - if (+elements[3] >= 0) { - values.solarPower = +elements[3]; - values.inverter = 0; - } else { - values.solarPower = 0; - values.inverter = -elements[3] - } - // calculated values - values.housePower = +elements[11]; - values.selfUsage = values.solarPower - values.gridPush; - if (values.selfUsage < 0) { - values.selfUsage = 0; - } - // charge points - var i; - values.lp0 = +elements[4]; - values.lp1 = +elements[5]; - for (i = 2; i < 9; i++) { - values["lp" + i] = +elements[11 + i]; - } - values.soc1 = +elements[9]; - values.soc2 = +elements[10]; - - // smart home - for (i = 0; i < 8; i++) { - if (!(wbdata.shDevice[i].countAsHouse)) { - values["sh" + i] = +elements[20 + i]; - } else { - values["sh" + i] = +0; - } - } - //consumers - values.co0 = +elements[12]; - values.co1 = +elements[13]; - //battery - if (+elements[7] > 0) { - values.batIn = +elements[7]; - values.batOut = 0; - } else if (+elements[7] < 0) { - values.batIn = 0; - values.batOut = -elements[7] - } else { - values.batIn = 0; - values.batOut = 0; - }; - values.batterySoc = +elements[8]; - - return values; - } - - extractDayValues(payload, oldPayload) { - const elements = payload.split(","); - const oldElements = oldPayload.split(","); - var values = {}; - values.date = new Date(d3.timeParse("%H%M")(elements[0])); - // evu - values.gridPull = this.calcValue(1, elements, oldElements); - values.gridPush = this.calcValue(2, elements, oldElements); - // pv - values.solarPower = this.calcValue(3, elements, oldElements); - values.inverter = 0; - // charge points - values.charging = this.calcValue(7, elements, oldElements); - var i; - for (i = 0; i < 3; i++) { - values["lp" + i] = this.calcValue(4 + i, elements, oldElements); - } - for (i = 3; i < 8; i++) { - values["lp" + i] = this.calcValue(12 + i, elements, oldElements); - } - values.lpSum = this.calcValue(7, elements, oldElements); - values.lpSumPv = this.calcValue(29, elements, oldElements); - values.soc1 = +elements[21]; - values.soc2 = +elements[22]; - // smart home - for (i = 0; i < 9; i++) { - if (!(wbdata.shDevice[i].countAsHouse)) { - values["sh" + i] = this.calcValue(26 + i, elements, oldElements); - } else { - values["sh" + i] = +0; - } - } - //consumers - values.co0 = this.calcValue(10, elements, oldElements); - values.co1 = this.calcValue(12, elements, oldElements); - //battery - values.batIn = this.calcValue(8, elements, oldElements); - values.batOut = this.calcValue(9, elements, oldElements); - values.batterySoc = +elements[20]; - // calculated values - values.housePower = values.gridPull + values.solarPower + values.batOut - - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 - - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8; - if (values.housePower < 0) { values.housePower = 0; }; - values.selfUsage = values.solarPower - values.gridPush; - if (values.selfUsage < 0) { values.selfUsage = 0; }; - if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { - values.chargingPv = values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) - } else { - values.chargingPv = 0; - } - return values; - } - - extractMonthValues(payload, oldPayload) { - if (payload != "0") { - const elements = payload.split(","); - const oldElements = oldPayload.split(","); - var values = {}; - values.date = new Date(d3.timeParse("%Y%m%d%H%M")(oldElements[0] + '1200')); - // evu - values.gridPull = this.calcMonthlyValue(1, elements, oldElements); - values.gridPush = this.calcMonthlyValue(2, elements, oldElements); - // pv - values.solarPower = this.calcMonthlyValue(3, elements, oldElements); - values.inverter = 0; - // charge points - values.charging = this.calcMonthlyValue(7, elements, oldElements); - values.chargingPv = this.calcMonthlyValue(29, elements, oldElements); - var i; - for (i = 0; i < 3; i++) { - values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); - } - for (i = 3; i < 8; i++) { - values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements, oldElements); - } - values.soc1 = +elements[21]; - values.soc2 = +elements[22]; - // smart home - for (i = 0; i < 10; i++) { - values["sh" + i] = this.calcMonthlyValue(19 + i, elements, oldElements); - } - //consumers - values.co0 = this.calcMonthlyValue(10, elements, oldElements); - values.co1 = this.calcMonthlyValue(12, elements, oldElements); - //battery - values.batIn = this.calcMonthlyValue(17, elements, oldElements); - values.batOut = this.calcMonthlyValue(18, elements, oldElements); - values.batterySoc = +elements[20]; - // calculated values - values.housePower = values.gridPull + values.solarPower + values.batOut - - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 - - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8 - values.sh9; if (values.housePower < 0) { values.housePower = 0; }; - values.selfUsage = values.solarPower - values.gridPush; - if (values.selfUsage < 0) { values.selfUsage = 0; }; - return values; - } else return {} - } - reset() { - this.resetLiveGraph(); - this.resetDayGraph(); - } - - resetLiveGraph() { - // fresh reload of the graph - this.initialized = false; - this.initCounter = 0; - this.initialGraphData = []; - this.graphData = []; - this.graphRefreshCounter = 0; - wbdata.historicSummary.chargingPv.energy = 0; - wbdata.usageSummary.chargingPv.energy = 0; - } - - resetDayGraph() { - this.initialized = false; - this.initCounter = 0; - this.staging = []; - this.rawData = []; - this.graphData = []; - } - - resetMonthGraph() { - this.initialized = false; - this.initCounter = false; - this.staging = []; - this.rawData = []; - this.graphData = []; - } - - calcValue(i, array, oldArray) { - var val = (array[i] - oldArray[i]) * 12; - if (val < 0 || val > 150000) { - val = 0; - } - return val; - } - calcMonthlyValue(i, array, oldArray) { - var val = (array[i] - oldArray[i]); - - if (val < 0 || val > 150000) { - val = 0; - } - return val; - } - - updateGraph() { - const svg = this.createOrUpdateSvg(); - this.drawChart(svg); - }; - - createOrUpdateSvg() { - this.svg.selectAll("*").remove(); - this.g = this.svg - .append("g") - .attr( - "transform", - "translate(" + this.margin.left + "," + this.margin.top + ")" - ); - return this.g; - } - - drawChart(svg) { - const height = this.height - this.margin.top - this.margin.bottom; - const width = this.width - this.margin.left - this.margin.right; - this.drawSourceGraph(svg, width, height / 2); - this.drawUsageGraph(svg, width, height / 2); - if (wbdata.graphMode != 'month') { - this.drawSoc(svg, width, height / 2); - } - this.drawXAxis(svg, width, height); - } - - drawSourceGraph(svg, width, height) { - var keys = (wbdata.graphMode == 'month') ? ["gridPull", "batOut", "selfUsage", "gridPush"] : ["selfUsage", "gridPush", "batOut", "gridPull"]; - - if (wbdata.graphMode == 'month') { - const dayRange = d3.extent(this.graphData, d => d.date.getDate()) - this.xScale = d3.scaleBand() - .domain(Array.from({ length: (dayRange[1] - dayRange[0] + 1) }, (v, k) => k + dayRange[0])) - .paddingInner(0.4); - } else { - this.xScale = d3.scaleTime().domain(d3.extent(this.graphData, d => d.date)); - } - this.xScale.range([0, width - this.margin.right]); - const yScale = d3.scaleLinear().range([height - 10, 0]); - const extent = d3.extent(this.graphData, (d) => - Math.max(d.solarPower + d.gridPull + d.batOut, d.selfUsage + d.gridPush)); - - yScale.domain([0, Math.ceil(extent[1] / 1000) * 1000]); - - const stackGen = d3.stack().keys(keys); - const stackedSeries = stackGen(this.graphData); - - if (wbdata.graphMode == 'month') { - var rects = svg.selectAll(".sourcebar") - .data(stackedSeries).enter() - .append("g") - .attr("fill", (d, i) => this.colors[keys[i]]) - .selectAll("rect") - .data((d) => d).enter() - .append("rect") - .attr("x", (d) => this.xScale(d.data.date.getDate())) - .attr("y", d => yScale(d[1])) - .attr("height", d => yScale(d[0]) - yScale(d[1])) - .attr("width", this.xScale.bandwidth()) - rects.append("svg:title").text((d) => formatWattH(d[1] - d[0])); - } else { - svg.selectAll(".sourceareas") - .data(stackedSeries) - .join("path") - .attr("d", d3.area() - .x((d, i) => this.xScale(this.graphData[i].date)) - .y0((d) => yScale(d[0])) - .y1((d) => yScale(d[1])) - ) - .attr("fill", (d, i) => this.colors[keys[i]]); - } - - const yAxis = svg.append("g") - .attr("class", "axis") - .call(d3.axisLeft(yScale) - .tickSizeInner(-(width - this.margin.right)) - .ticks(4) - .tickFormat((d, i) => (d == 0) ? "" : (Math.round(d / 100) / 10))) - ; - yAxis.selectAll(".tick") - .attr("font-size", 12); - - if (wbdata.showGrid) { - yAxis.selectAll(".tick line") - .attr("stroke", this.gridcolor) - .attr("stroke-width", "0.5"); - } else { - yAxis.selectAll(".tick line").attr("stroke", this.bgcolor); - } - yAxis.select(".domain") - .attr("stroke", this.bgcolor) - ; - } - - drawUsageGraph(svg, width, height) { - const yScale = d3.scaleLinear().range([height + 10, 2 * height]); - - const extent = d3.extent(this.graphData, (d) => - (d.housePower + d.lp0 + d.lp1 + d.lp2 + d.lp3 + d.lp4 - + d.lp5 + d.lp6 + d.lp7 + d.sh0 + d.sh1 + d.sh2 + d.sh3 + d.sh4 - + d.sh5 + d.sh6 + d.sh7 + d.co0 + d.co1 + d.batIn + d.inverter) - ); - yScale.domain([0, Math.ceil(extent[1] / 1000) * 1000]); - const keys = [["lp0", "lp1", "lp2", "lp3", "lp4", - "lp5", "lp6", "lp7", - "sh0", "sh1", "sh2", "sh3", "sh4", - "sh5", "sh6", "sh7", "co0", "co1", "housePower", "batIn", "inverter"], - ["housePower", "lp0", "lp1", "lp2", "lp3", "lp4", - "lp5", "lp6", "lp7", - "sh0", "sh1", "sh2", "sh3", "sh4", - "sh5", "sh6", "sh7", "co0", "co1", "batIn", "inverter"], - ["sh0", "sh1", "sh2", "sh3", "sh4", - "sh5", "sh6", "sh7", "co0", "co1", "housePower", "lp0", "lp1", "lp2", "lp3", "lp4", - "lp5", "lp6", "lp7", - "batIn", "inverter"] - ]; - - const stackGen = d3.stack().keys(keys[wbdata.usageStackOrder]); - const stackedSeries = stackGen(this.graphData); - if (wbdata.graphMode == 'month') { - var rects2 = svg.selectAll(".sourcebar") - .data(stackedSeries).enter() - .append("g") - .attr("fill", (d, i) => this.colors[keys[wbdata.usageStackOrder][i]]) - .selectAll("rect") - .data(d => d).enter() - .append("rect") - .attr("x", (d) => this.xScale(d.data.date.getDate())) - .attr("y", d => yScale(d[0])) - .attr("height", d => yScale(d[1]) - yScale(d[0])) - .attr("width", this.xScale.bandwidth()) - rects2.append("svg:title").text((d) => formatWattH(d[1] - d[0])); - } else { - svg.selectAll(".targetareas") - .data(stackedSeries) - .join("path") - .attr("d", d3.area() - .x((d, i) => this.xScale(this.graphData[i].date)) - .y0((d) => yScale(d[0])) - .y1((d) => yScale(d[1])) - ) - .attr("fill", (d, i) => this.colors[keys[wbdata.usageStackOrder][i]]); - } - const yAxis = svg.append("g") - .attr("class", "axis") - .call(d3.axisLeft(yScale) - .tickSizeInner(-(width - this.margin.right)) - .ticks(4) - .tickFormat((d, i) => (d == 0) ? "" : (Math.round(d / 100) / 10)) - ); - yAxis.selectAll(".tick") - .attr("font-size", 12); - if (wbdata.showGrid) { - yAxis.selectAll(".tick line") - .attr("stroke", this.gridcolor) - .attr("stroke-width", "0.5"); - } else { - yAxis.selectAll(".tick line").attr("stroke", this.bgcolor); - } - yAxis.select(".domain") - .attr("stroke", this.bgcolor) - ; - } - - drawXAxis(svg, width, height) { - const fontsize = 12; - const xScale = d3.scaleTime().range([0, width - this.margin.right]); - xScale.domain(d3.extent(this.graphData, (d) => d.date)); - - var ticksize = (wbdata.showGrid) ? -(height / 2 - 7) : -10 - if (wbdata.graphMode == 'month') { - ticksize = 0; - } - const xAxisGenerator = d3 - .axisBottom(this.xScale) - .ticks(4) - .tickSizeInner(ticksize) - - if (wbdata.graphMode != 'month') { - xAxisGenerator.tickFormat(d3.timeFormat("%H:%M")) - .ticks(4); - } - - const xAxis = svg.append("g").attr("class", "axis") - .call(xAxisGenerator); - xAxis.attr("transform", "translate(0," + (height / 2 - 6) + ")"); - xAxis.selectAll(".tick") - .attr("color", this.axiscolor) - .attr("font-size", fontsize); - if (wbdata.showGrid) { - xAxis.selectAll(".tick line") - .attr("stroke", this.gridcolor) - .attr("stroke-width", "0.5"); - } else { - xAxis.selectAll(".tick line").attr("stroke", this.bgcolor); - } - xAxis.select(".domain") - .attr("stroke", this.bgcolor) - ; - svg.append("text") - .attr("x", - this.margin.left) - .attr("y", height / 2 + 5) - .attr("fill", this.axiscolor) - .attr("font-size", fontsize) - .text("kW") - - if (wbdata.showGrid) { - // second x axis for the grid - const ticksize2 = -(height / 2 - 10); - const xAxisGenerator2 = d3 - .axisTop(xScale) - .ticks(4) - .tickSizeInner(ticksize2) - .tickFormat(""); - const xAxis2 = svg.append("g").attr("class", "axis") - .call(xAxisGenerator2); - xAxis2.attr("transform", "translate(0," + (height / 2 + 10) + ")"); - xAxis2.selectAll(".tick") - .attr("color", this.axiscolor) - .attr("font-size", fontsize); - xAxis2.selectAll(".tick line").attr("stroke", this.gridcolor).attr("stroke-width", "0.5"); - - - xAxis2.select(".domain") - .attr("stroke", this.bgcolor) - ; - // add a rectangle around the graph - svg.append("g") - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", width - this.margin.right) - .attr("height", height) - .attr("fill", "none") - .attr("stroke", this.gridcolor) - .attr("stroke-width", "0.5"); - } - } - - drawSoc(svg, width, height) { - const xScale = d3.scaleTime().range([0, width - this.margin.right]); - const yScale = d3.scaleLinear().range([height - 10, 0]); - xScale.domain(d3.extent(this.graphData, (d) => d.date)); - yScale.domain([0, 100]); - // Chargepoint 1 - if (wbdata.chargePoint[0].isSocConfigured) { - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.bgcolor) - .attr("stroke-width", 1) - .attr("fill", "none") - //.style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.soc1)) - ); - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.lp1color) - .attr("stroke-width", 1) - .attr("fill", "none") - .style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.soc1)) - ); - svg.append("text") - .attr("x", width - this.margin.right - 3) - .attr("y", yScale(this.graphData[this.graphData.length - 1].soc1 + 2)) - .text(wbdata.chargePoint[0].name) - .attr("fill", this.lp1color) - .style("font-size", 10) - .attr("text-anchor", "end"); - } - // Chargepoint 2 - if (wbdata.chargePoint[1].isSocConfigured) { - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.bgcolor) - .attr("stroke-width", 1) - .attr("fill", "none") - // .style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.soc2)) - ); - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.lp2color) - .attr("stroke-width", 1) - .attr("fill", "none") - .style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.soc2)) - ); - svg.append("text") - .attr("x", 3) - .attr("y", yScale(this.graphData[this.graphData.length - 1].soc2 + 2)) - .text(wbdata.chargePoint[1].name) - .attr("fill", this.lp2color) - .style("font-size", 10) - .attr("text-anchor", "start"); - } - // Battery - if (wbdata.isBatteryConfigured) { - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.bgcolor) - .attr("stroke-width", 2) - .attr("fill", "none") - //.style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.batterySoc)) - ); - svg.append("path") - .datum(this.graphData) - .attr("stroke", this.batSocColor) - .attr("stroke-width", 2) - .attr("fill", "none") - .style("stroke-dasharray", ("3, 3")) - .attr("d", d3.line() - .x((d, i) => xScale(this.graphData[i].date)) - .y(d => yScale(d.batterySoc)) - ); - svg.append("text") - .attr("x", (width - this.margin.right) / 2) - .attr("y", yScale(this.graphData[this.graphData.length - 1].batterySoc + 2)) - .text("Speicher") - .attr("fill", this.batteryColor) - .style("background-color", "black") - .style("font-size", 10) - .attr("text-anchor", "middle"); - } - const socAxis = svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(" + (width - 20) + ",0)") - .call(d3.axisRight(yScale) - .ticks(5) - .tickFormat((d) => (d + "%"))) - ; - socAxis.selectAll(".tick").attr("font-size", 12); - socAxis.selectAll(".tick line").attr("stroke", this.bgcolor); - socAxis.select(".domain") - .attr("stroke", this.bgcolor) - ; - } - - getEnergyValues() { - - } + svg; + xScale; + + constructor() { + this.graphData = []; + this.initCounter = 0; + this.staging = []; + this.rawData = []; + this.graphdata = [] + this.initialGraphData = []; + this.initialized = false; + this.colors = []; + this.gridColors = {}; + this.bgcolor = ""; + this.axiscolor = ""; + this.chargeColor = ""; + this.lp1color = ""; + this.lp2color = ""; + this.batteryColor = ""; + this.batSocColor = ""; + this.graphRefreshCounter = 0; + this.width = 500; + this.height = 500; + this.margin = { top: 10, right: 20, bottom: 10, left: 25 }; + this.liveGraphMinutes = 0; + wbdata.usageStackOrder = 2; + } + + init() { + var style = getComputedStyle(document.body); + this.colors.housePower = 'var(--color-house)'; + this.colors.batIn = 'var(--color-battery)'; + this.colors.inverter = 'var(--color-pv)'; + this.gridColors[0] = 'var(--color-battery)'; + this.colors.batOut = 'var(--color-battery)'; + this.gridColors[1] = 'var(--color-pv)'; + this.colors.selfUsage = 'var(--color-pv)'; + this.gridColors[2] = 'var(--color-export)'; + this.colors.gridPush = 'var(--color-export)'; + this.gridColors[3] = 'var(--color-evu)'; + this.colors.gridPull = 'var(--color-evu)'; + this.bgcolor = 'var(--color-bg)'; + this.chargeColor = 'var(--color-charging)'; + this.axiscolor = 'var(--color-axis)'; + this.gridcolor = 'var(--color-grid)'; + this.lp1color = 'var(--color-lp1)'; + this.lp2color = 'var(--color-lp2)'; + this.batteryColor = 'var(--color-battery)'; + this.batSocColor = 'var(--color-title)'; + var i; + for (i = 0; i < 8; i++) { + this.colors["lp" + i] = wbdata.chargePoint[i].color; + } + for (i = 0; i < 8; i++) { + this.colors["sh" + i] = wbdata.shDevice[i].color; + } + this.colors.co0 = 'var(--color-co1)'; + this.colors.co1 = 'var(--color-co2)'; + + var figure = d3.select("figure#powergraph"); + this.svg = figure.append("svg") + .attr("viewBox", `0 0 500 500`); + + d3.select("button#graphLeftButton") + .on("click", shiftLeft) + d3.select("button#graphRightButton") + .on("click", shiftRight) + d3.select("button#graphChangeButton") + .on("click", changeStack) + d3.select("button#gridChangeButton") + .on("click", toggleGrid) + } + + activateLive() { + try { + this.resetLiveGraph(); + subscribeMqttGraphSegments(); + subscribeGraphUpdates(); + } catch (err) { + // on initial invocation this method is not existing + } + this.updateHeading(); + } + + deactivateLive() { + try { + unsubscribeMqttGraphSegments(); + unsubscribeGraphUpdates(); + } catch (err) { + // on initial run this method is not existing + } + } + + activateDay() { + if (wbdata.graphMode == 'day') { + if (wbdata.showTodayGraph) { + wbdata.graphDate = new Date(); // ensure we update todays date if day changes during display + } + this.resetDayGraph(); + try { + subscribeDayGraph(wbdata.graphDate); + } catch (err) { + //on initial run of activate, subscribeDayGraph is not yet initialized. + // the error can be ignored + } + this.updateHeading(); + } + } + + updateHeading() { + var heading = "Leistung / Ladestand "; + switch (wbdata.graphMode) { + case 'live': + heading = heading + this.liveGraphMinutes + " min"; + break; + case 'day': + const today = new Date(); + if (today.getDate() == wbdata.graphDate.getDate() && today.getMonth() == wbdata.graphDate.getMonth() && today.getFullYear() == wbdata.graphDate.getFullYear()) { + heading = heading + "heute"; + } else { + heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; + } + break; + case 'month': + heading = "Tageswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); + break; + default: break; + } + d3.select("h3#graphheading").text(heading); + } + + deactivateDay() { + try { + unsubscribeDayGraph(); + } catch (err) { + // ignore error + } + } + + activateMonth() { + this.resetMonthGraph(); + try { + subscribeMonthGraph(wbdata.graphMonth); + } catch (err) { + //on initial run of activate, subscribeDayGraph is not yet initialized. + // the error can be ignored + console.error("subscribeMonthGraph failed") + } + this.updateHeading(); + } + + deactivateMonth() { + try { + unsubscribeMonthGraph(); + } catch (err) { + // ignore error + } + } + + updateLive(topic, payload) { + if (wbdata.graphMode == 'live') { // only update if live graph is active + if (this.initialized) { // steady state + if (topic === "openWB/graph/lastlivevalues") { + const values = this.extractLiveValues(payload.toString()); + this.graphRefreshCounter++; + this.graphData.push(values); + this.updateGraph(); + if (this.graphRefreshCounter > 60) { + this.resetLiveGraph(); + subscribeMqttGraphSegments(); + } + } + } else { // init phase + const t = topic; + if (t.substring(t.length - 13, t.length) === "alllivevalues") { + // init message + const serialNo = t.substring(13, t.length - 13); + var bulkdata = payload.toString().split("\n"); + if (bulkdata.length <= 1) { + bulkdata = []; + } + if (serialNo != "") { + if (typeof (this.initialGraphData[+serialNo - 1]) === 'undefined') { + this.initialGraphData[+serialNo - 1] = bulkdata; + this.initCounter++; + } + } + if (this.initCounter == 16) {// Initialization complete + this.initialized = true; + this.initialGraphData.map(bulkdata => { + bulkdata.map((line) => { + const values = this.extractLiveValues(line); + this.graphData.push(values); + }); + }); + const startTime = this.graphData[0].date; + const endTime = this.graphData[this.graphData.length - 1].date; + this.liveGraphMinutes = Math.round((endTime - startTime) / 60000); + this.updateHeading(); + this.updateGraph(); + unsubscribeMqttGraphSegments(); + } + } + } + } + } + + updateDay(topic, payload) { + var segment; + if (payload == 'empty') { + segment = []; + } else { + segment = payload.toString().split("\n"); + if (segment[0] == "") { + segment = []; + } + const serialNo = topic.substring(26, topic.length); + if (serialNo != "") { + if (typeof (this.staging[+serialNo - 1]) === 'undefined') { + this.staging[+serialNo - 1] = segment; + this.initCounter++; + } + } + } + if (this.initCounter == 12) {// Initialization complete + unsubscribeDayGraph(); + + this.initCounter = 0; + this.staging.map(segment => + segment.map(line => this.rawData.push(line)) + ) + this.rawData.map((line, i, a) => { + if (i > 0) { + const values = this.extractDayValues(line, a[i - 1]); + this.graphData.push(values); + } else { + // const values = this.extractValues(line, []); + } + }); + this.updateGraph(); + this.updateEnergyValues(); + wbdata.dayGraphUpdated(); + setTimeout(() => this.activateDay(), 300000) + } + } + + updateMonth(topic, payload) { + if (payload != 'empty') { + var segment = payload.toString().split("\n"); + if (segment[0] == "") { + segment = []; + } + const serialNo = topic.substring(29, topic.length); + if (serialNo != "") { + if (typeof (this.staging[+serialNo - 1]) === 'undefined') { + this.staging[+serialNo - 1] = segment; + this.initCounter++; + } + } + if (this.initCounter == 12) {// Initialization complete + unsubscribeMonthGraph(); + this.initCounter = 0; + this.staging.map((segment, i) => { + if (i == 3) { + segment.map((line, i) => { + if (line.length > 1) { this.rawData.push(line) } + }) + } + }) + this.rawData.map((line, i, a) => { + if (i > 0) { + if (line != "0" && line != "") { + const values = this.extractMonthValues(line, a[i - 1]); + if ((values.date.getFullYear() == wbdata.graphMonth.year) + && ((values.date.getMonth() == wbdata.graphMonth.month) + || ((values.date.getMonth() == (wbdata.graphMonth.month + 1)) && (values.date.getDate == 1)) + ) + ) { this.graphData.push(values); } + } + } else { + // const values = this.extractValues(line, []); + } + }); + this.updateGraph(); + this.updateEnergyValues(); + wbdata.monthGraphUpdated(); + // setTimeout(() => this.activateDay(), 300000) + } + } + } + updateEnergyValues() { + if (this.rawData.length) { + const startValues = this.rawData[0].split(','); + const endValues = this.rawData[this.rawData.length - 1].split(','); + wbdata.historicSummary.pv.energy = (endValues[3] - startValues[3]) / 1000; + wbdata.historicSummary.evuIn.energy = (endValues[1] - startValues[1]) / 1000; + wbdata.historicSummary.batOut.energy = (endValues[9] - startValues[9]) / 1000; + wbdata.historicSummary.evuOut.energy = (endValues[2] - startValues[2]) / 1000; + wbdata.historicSummary.charging.energy = (endValues[7] - startValues[7]) / 1000; + var deviceEnergySum = 0; + var deviceEnergy = 0; + let deviceIndex = (wbdata.graphMode == 'day') ? 26 : 19; + for (var i = 0; i < 9; i++) { + deviceEnergy = (endValues[deviceIndex + i] - startValues[deviceIndex + i]) / 1000; + if (deviceEnergy < 0) { deviceEnergy = 0 } + deviceEnergySum = deviceEnergySum + deviceEnergy + wbdata.historicSummary['sh' + i].energy = deviceEnergy + } + deviceEnergySum = deviceEnergySum + (endValues[10] - startValues[10]) / 1000; + deviceEnergySum = deviceEnergySum + (endValues[12] - startValues[12]) / 1000; + wbdata.historicSummary.devices.energy = deviceEnergySum; + wbdata.historicSummary.batIn.energy = (endValues[8] - startValues[8]) / 1000; + wbdata.historicSummary.house.energy = wbdata.historicSummary.evuIn.energy + wbdata.historicSummary.pv.energy + wbdata.historicSummary.batOut.energy + - wbdata.historicSummary.evuOut.energy - wbdata.historicSummary.batIn.energy - wbdata.historicSummary.charging.energy - wbdata.historicSummary.devices.energy; + + let pvCharged = this.graphData.reduce((prev, cur) => { + return prev + (cur.chargingPv / 12); + }, 0) + wbdata.historicSummary.chargingPv.energy = pvCharged / 1000; + wbdata.usageSummary.chargingPv.energy = pvCharged / 1000; + } + } + extractLiveValues(payload) { + const elements = payload.split(","); + const now = new Date(Date.now()); + const mSecondsPerDay = 86400000 // milliseconds in a day + var values = {}; + values.date = new Date(d3.timeParse("%H:%M:%S")(elements[0])); + values.date.setDate(now.getDate()) + values.date.setMonth(now.getMonth()) + values.date.setFullYear(now.getFullYear()) + if (values.date.getHours() > now.getHours()) { // this is an entry from yesterday + values.date = new Date(values.date.getTime() - mSecondsPerDay) // change date to yesterday + } + // evu + if (+elements[1] > 0) { + values.gridPull = +elements[1]; + values.gridPush = 0; + } else { + values.gridPull = 0; + values.gridPush = -elements[1]; + } + // pv + if (+elements[3] >= 0) { + values.solarPower = +elements[3]; + values.inverter = 0; + } else { + values.solarPower = 0; + values.inverter = -elements[3] + } + // calculated values + values.housePower = +elements[11]; + values.selfUsage = values.solarPower - values.gridPush; + if (values.selfUsage < 0) { + values.selfUsage = 0; + } + // charge points + var i; + values.lp0 = +elements[4]; + values.lp1 = +elements[5]; + for (i = 2; i < 9; i++) { + values["lp" + i] = +elements[11 + i]; + } + values.soc1 = +elements[9]; + values.soc2 = +elements[10]; + + // smart home + for (i = 0; i < 8; i++) { + if (!(wbdata.shDevice[i].countAsHouse)) { + values["sh" + i] = +elements[20 + i]; + } else { + values["sh" + i] = +0; + } + } + //consumers + values.co0 = +elements[12]; + values.co1 = +elements[13]; + //battery + if (+elements[7] > 0) { + values.batIn = +elements[7]; + values.batOut = 0; + } else if (+elements[7] < 0) { + values.batIn = 0; + values.batOut = -elements[7] + } else { + values.batIn = 0; + values.batOut = 0; + }; + values.batterySoc = +elements[8]; + + return values; + } + + extractDayValues(payload, oldPayload) { + const elements = payload.split(","); + const oldElements = oldPayload.split(","); + var values = {}; + values.date = new Date(d3.timeParse("%H%M")(elements[0])); + // evu + values.gridPull = this.calcValue(1, elements, oldElements); + values.gridPush = this.calcValue(2, elements, oldElements); + // pv + values.solarPower = this.calcValue(3, elements, oldElements); + values.inverter = 0; + // charge points + values.charging = this.calcValue(7, elements, oldElements); + var i; + for (i = 0; i < 3; i++) { + values["lp" + i] = this.calcValue(4 + i, elements, oldElements); + } + for (i = 3; i < 8; i++) { + values["lp" + i] = this.calcValue(12 + i, elements, oldElements); + } + values.lpSum = this.calcValue(7, elements, oldElements); + values.lpSumPv = this.calcValue(29, elements, oldElements); + values.soc1 = +elements[21]; + values.soc2 = +elements[22]; + // smart home + for (i = 0; i < 9; i++) { + if (!(wbdata.shDevice[i].countAsHouse)) { + values["sh" + i] = this.calcValue(26 + i, elements, oldElements); + } else { + values["sh" + i] = +0; + } + } + //consumers + values.co0 = this.calcValue(10, elements, oldElements); + values.co1 = this.calcValue(12, elements, oldElements); + //battery + values.batIn = this.calcValue(8, elements, oldElements); + values.batOut = this.calcValue(9, elements, oldElements); + values.batterySoc = +elements[20]; + // calculated values + values.housePower = values.gridPull + values.solarPower + values.batOut + - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 + - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8; + if (values.housePower < 0) { values.housePower = 0; }; + values.selfUsage = values.solarPower - values.gridPush; + if (values.selfUsage < 0) { values.selfUsage = 0; }; + if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { + values.chargingPv = values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) + } else { + values.chargingPv = 0; + } + return values; + } + + extractMonthValues(payload, oldPayload) { + if (payload != "0") { + const elements = payload.split(","); + const oldElements = oldPayload.split(","); + var values = {}; + values.date = new Date(d3.timeParse("%Y%m%d%H%M")(oldElements[0] + '1200')); + // evu + values.gridPull = this.calcMonthlyValue(1, elements, oldElements); + values.gridPush = this.calcMonthlyValue(2, elements, oldElements); + // pv + values.solarPower = this.calcMonthlyValue(3, elements, oldElements); + values.inverter = 0; + // charge points + values.charging = this.calcMonthlyValue(7, elements, oldElements); + values.chargingPv = this.calcMonthlyValue(29, elements, oldElements); + var i; + for (i = 0; i < 3; i++) { + values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); + } + for (i = 3; i < 8; i++) { + values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements, oldElements); + } + values.soc1 = +elements[21]; + values.soc2 = +elements[22]; + // smart home + for (i = 0; i < 10; i++) { + values["sh" + i] = this.calcMonthlyValue(19 + i, elements, oldElements); + } + //consumers + values.co0 = this.calcMonthlyValue(10, elements, oldElements); + values.co1 = this.calcMonthlyValue(12, elements, oldElements); + //battery + values.batIn = this.calcMonthlyValue(17, elements, oldElements); + values.batOut = this.calcMonthlyValue(18, elements, oldElements); + values.batterySoc = +elements[20]; + // calculated values + values.housePower = values.gridPull + values.solarPower + values.batOut + - values.gridPush - values.batIn - values.charging - values.co0 - values.co1 + - values.sh0 - values.sh1 - values.sh2 - values.sh3 - values.sh4 - values.sh5 - values.sh6 - values.sh7 - values.sh8 - values.sh9; if (values.housePower < 0) { values.housePower = 0; }; + values.selfUsage = values.solarPower - values.gridPush; + if (values.selfUsage < 0) { values.selfUsage = 0; }; + return values; + } else return {} + } + reset() { + this.resetLiveGraph(); + this.resetDayGraph(); + } + + resetLiveGraph() { + // fresh reload of the graph + this.initialized = false; + this.initCounter = 0; + this.initialGraphData = []; + this.graphData = []; + this.graphRefreshCounter = 0; + wbdata.historicSummary.chargingPv.energy = 0; + wbdata.usageSummary.chargingPv.energy = 0; + } + + resetDayGraph() { + this.initialized = false; + this.initCounter = 0; + this.staging = []; + this.rawData = []; + this.graphData = []; + } + + resetMonthGraph() { + this.initialized = false; + this.initCounter = false; + this.staging = []; + this.rawData = []; + this.graphData = []; + } + + calcValue(i, array, oldArray) { + var val = (array[i] - oldArray[i]) * 12; + if (val < 0 || val > 150000) { + val = 0; + } + return val; + } + calcMonthlyValue(i, array, oldArray) { + var val = (array[i] - oldArray[i]); + + if (val < 0 || val > 150000) { + val = 0; + } + return val; + } + + updateGraph() { + const svg = this.createOrUpdateSvg(); + this.drawChart(svg); + }; + + createOrUpdateSvg() { + this.svg.selectAll("*").remove(); + this.g = this.svg + .append("g") + .attr( + "transform", + "translate(" + this.margin.left + "," + this.margin.top + ")" + ); + return this.g; + } + + drawChart(svg) { + const height = this.height - this.margin.top - this.margin.bottom; + const width = this.width - this.margin.left - this.margin.right; + this.drawSourceGraph(svg, width, height / 2); + this.drawUsageGraph(svg, width, height / 2); + if (wbdata.graphMode != 'month') { + this.drawSoc(svg, width, height / 2); + } + this.drawXAxis(svg, width, height); + } + + drawSourceGraph(svg, width, height) { + var keys = (wbdata.graphMode == 'month') ? ["gridPull", "batOut", "selfUsage", "gridPush"] : ["selfUsage", "gridPush", "batOut", "gridPull"]; + + if (wbdata.graphMode == 'month') { + const dayRange = d3.extent(this.graphData, d => d.date.getDate()) + this.xScale = d3.scaleBand() + .domain(Array.from({ length: (dayRange[1] - dayRange[0] + 1) }, (v, k) => k + dayRange[0])) + .paddingInner(0.4); + } else { + this.xScale = d3.scaleTime().domain(d3.extent(this.graphData, d => d.date)); + } + this.xScale.range([0, width - this.margin.right]); + const yScale = d3.scaleLinear().range([height - 10, 0]); + const extent = d3.extent(this.graphData, (d) => + Math.max(d.solarPower + d.gridPull + d.batOut, d.selfUsage + d.gridPush)); + + yScale.domain([0, Math.ceil(extent[1] / 1000) * 1000]); + + const stackGen = d3.stack().keys(keys); + const stackedSeries = stackGen(this.graphData); + + if (wbdata.graphMode == 'month') { + var rects = svg.selectAll(".sourcebar") + .data(stackedSeries).enter() + .append("g") + .attr("fill", (d, i) => this.colors[keys[i]]) + .selectAll("rect") + .data((d) => d).enter() + .append("rect") + .attr("x", (d) => this.xScale(d.data.date.getDate())) + .attr("y", d => yScale(d[1])) + .attr("height", d => yScale(d[0]) - yScale(d[1])) + .attr("width", this.xScale.bandwidth()) + rects.append("svg:title").text((d) => formatWattH(d[1] - d[0])); + } else { + svg.selectAll(".sourceareas") + .data(stackedSeries) + .join("path") + .attr("d", d3.area() + .x((d, i) => this.xScale(this.graphData[i].date)) + .y0((d) => yScale(d[0])) + .y1((d) => yScale(d[1])) + ) + .attr("fill", (d, i) => this.colors[keys[i]]); + } + + const yAxis = svg.append("g") + .attr("class", "axis") + .call(d3.axisLeft(yScale) + .tickSizeInner(-(width - this.margin.right)) + .ticks(4) + .tickFormat((d, i) => (d == 0) ? "" : (Math.round(d / 100) / 10))) + ; + yAxis.selectAll(".tick") + .attr("font-size", 12); + + if (wbdata.showGrid) { + yAxis.selectAll(".tick line") + .attr("stroke", this.gridcolor) + .attr("stroke-width", "0.5"); + } else { + yAxis.selectAll(".tick line").attr("stroke", this.bgcolor); + } + yAxis.select(".domain") + .attr("stroke", this.bgcolor) + ; + } + + drawUsageGraph(svg, width, height) { + const yScale = d3.scaleLinear().range([height + 10, 2 * height]); + + const extent = d3.extent(this.graphData, (d) => + (d.housePower + d.lp0 + d.lp1 + d.lp2 + d.lp3 + d.lp4 + + d.lp5 + d.lp6 + d.lp7 + d.sh0 + d.sh1 + d.sh2 + d.sh3 + d.sh4 + + d.sh5 + d.sh6 + d.sh7 + d.co0 + d.co1 + d.batIn + d.inverter) + ); + yScale.domain([0, Math.ceil(extent[1] / 1000) * 1000]); + const keys = [["lp0", "lp1", "lp2", "lp3", "lp4", + "lp5", "lp6", "lp7", + "sh0", "sh1", "sh2", "sh3", "sh4", + "sh5", "sh6", "sh7", "co0", "co1", "housePower", "batIn", "inverter"], + ["housePower", "lp0", "lp1", "lp2", "lp3", "lp4", + "lp5", "lp6", "lp7", + "sh0", "sh1", "sh2", "sh3", "sh4", + "sh5", "sh6", "sh7", "co0", "co1", "batIn", "inverter"], + ["sh0", "sh1", "sh2", "sh3", "sh4", + "sh5", "sh6", "sh7", "co0", "co1", "housePower", "lp0", "lp1", "lp2", "lp3", "lp4", + "lp5", "lp6", "lp7", + "batIn", "inverter"] + ]; + + const stackGen = d3.stack().keys(keys[wbdata.usageStackOrder]); + const stackedSeries = stackGen(this.graphData); + if (wbdata.graphMode == 'month') { + var rects2 = svg.selectAll(".sourcebar") + .data(stackedSeries).enter() + .append("g") + .attr("fill", (d, i) => this.colors[keys[wbdata.usageStackOrder][i]]) + .selectAll("rect") + .data(d => d).enter() + .append("rect") + .attr("x", (d) => this.xScale(d.data.date.getDate())) + .attr("y", d => yScale(d[0])) + .attr("height", d => yScale(d[1]) - yScale(d[0])) + .attr("width", this.xScale.bandwidth()) + rects2.append("svg:title").text((d) => formatWattH(d[1] - d[0])); + } else { + svg.selectAll(".targetareas") + .data(stackedSeries) + .join("path") + .attr("d", d3.area() + .x((d, i) => this.xScale(this.graphData[i].date)) + .y0((d) => yScale(d[0])) + .y1((d) => yScale(d[1])) + ) + .attr("fill", (d, i) => this.colors[keys[wbdata.usageStackOrder][i]]); + } + const yAxis = svg.append("g") + .attr("class", "axis") + .call(d3.axisLeft(yScale) + .tickSizeInner(-(width - this.margin.right)) + .ticks(4) + .tickFormat((d, i) => (d == 0) ? "" : (Math.round(d / 100) / 10)) + ); + yAxis.selectAll(".tick") + .attr("font-size", 12); + if (wbdata.showGrid) { + yAxis.selectAll(".tick line") + .attr("stroke", this.gridcolor) + .attr("stroke-width", "0.5"); + } else { + yAxis.selectAll(".tick line").attr("stroke", this.bgcolor); + } + yAxis.select(".domain") + .attr("stroke", this.bgcolor) + ; + } + + drawXAxis(svg, width, height) { + const fontsize = 12; + const xScale = d3.scaleTime().range([0, width - this.margin.right]); + xScale.domain(d3.extent(this.graphData, (d) => d.date)); + + var ticksize = (wbdata.showGrid) ? -(height / 2 - 7) : -10 + if (wbdata.graphMode == 'month') { + ticksize = 0; + } + const xAxisGenerator = d3 + .axisBottom(this.xScale) + .ticks(4) + .tickSizeInner(ticksize) + + if (wbdata.graphMode != 'month') { + xAxisGenerator.tickFormat(d3.timeFormat("%H:%M")) + .ticks(4); + } + + const xAxis = svg.append("g").attr("class", "axis") + .call(xAxisGenerator); + xAxis.attr("transform", "translate(0," + (height / 2 - 6) + ")"); + xAxis.selectAll(".tick") + .attr("color", this.axiscolor) + .attr("font-size", fontsize); + if (wbdata.showGrid) { + xAxis.selectAll(".tick line") + .attr("stroke", this.gridcolor) + .attr("stroke-width", "0.5"); + } else { + xAxis.selectAll(".tick line").attr("stroke", this.bgcolor); + } + xAxis.select(".domain") + .attr("stroke", this.bgcolor) + ; + svg.append("text") + .attr("x", - this.margin.left) + .attr("y", height / 2 + 5) + .attr("fill", this.axiscolor) + .attr("font-size", fontsize) + .text("kW") + + if (wbdata.showGrid) { + // second x axis for the grid + const ticksize2 = -(height / 2 - 10); + const xAxisGenerator2 = d3 + .axisTop(xScale) + .ticks(4) + .tickSizeInner(ticksize2) + .tickFormat(""); + const xAxis2 = svg.append("g").attr("class", "axis") + .call(xAxisGenerator2); + xAxis2.attr("transform", "translate(0," + (height / 2 + 10) + ")"); + xAxis2.selectAll(".tick") + .attr("color", this.axiscolor) + .attr("font-size", fontsize); + xAxis2.selectAll(".tick line").attr("stroke", this.gridcolor).attr("stroke-width", "0.5"); + + + xAxis2.select(".domain") + .attr("stroke", this.bgcolor) + ; + // add a rectangle around the graph + svg.append("g") + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", width - this.margin.right) + .attr("height", height) + .attr("fill", "none") + .attr("stroke", this.gridcolor) + .attr("stroke-width", "0.5"); + } + } + + drawSoc(svg, width, height) { + const xScale = d3.scaleTime().range([0, width - this.margin.right]); + const yScale = d3.scaleLinear().range([height - 10, 0]); + xScale.domain(d3.extent(this.graphData, (d) => d.date)); + yScale.domain([0, 100]); + // Chargepoint 1 + if (wbdata.chargePoint[0].isSocConfigured) { + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.bgcolor) + .attr("stroke-width", 1) + .attr("fill", "none") + //.style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.soc1)) + ); + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.lp1color) + .attr("stroke-width", 1) + .attr("fill", "none") + .style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.soc1)) + ); + svg.append("text") + .attr("x", width - this.margin.right - 3) + .attr("y", yScale(this.graphData[this.graphData.length - 1].soc1 + 2)) + .text(wbdata.chargePoint[0].name) + .attr("fill", this.lp1color) + .style("font-size", 10) + .attr("text-anchor", "end"); + } + // Chargepoint 2 + if (wbdata.chargePoint[1].isSocConfigured) { + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.bgcolor) + .attr("stroke-width", 1) + .attr("fill", "none") + // .style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.soc2)) + ); + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.lp2color) + .attr("stroke-width", 1) + .attr("fill", "none") + .style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.soc2)) + ); + svg.append("text") + .attr("x", 3) + .attr("y", yScale(this.graphData[this.graphData.length - 1].soc2 + 2)) + .text(wbdata.chargePoint[1].name) + .attr("fill", this.lp2color) + .style("font-size", 10) + .attr("text-anchor", "start"); + } + // Battery + if (wbdata.isBatteryConfigured) { + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.bgcolor) + .attr("stroke-width", 2) + .attr("fill", "none") + //.style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.batterySoc)) + ); + svg.append("path") + .datum(this.graphData) + .attr("stroke", this.batSocColor) + .attr("stroke-width", 2) + .attr("fill", "none") + .style("stroke-dasharray", ("3, 3")) + .attr("d", d3.line() + .x((d, i) => xScale(this.graphData[i].date)) + .y(d => yScale(d.batterySoc)) + ); + svg.append("text") + .attr("x", (width - this.margin.right) / 2) + .attr("y", yScale(this.graphData[this.graphData.length - 1].batterySoc + 2)) + .text("Speicher") + .attr("fill", this.batteryColor) + .style("background-color", "black") + .style("font-size", 10) + .attr("text-anchor", "middle"); + } + const socAxis = svg.append("g") + .attr("class", "axis") + .attr("transform", "translate(" + (width - 20) + ",0)") + .call(d3.axisRight(yScale) + .ticks(5) + .tickFormat((d) => (d + "%"))) + ; + socAxis.selectAll(".tick").attr("font-size", 12); + socAxis.selectAll(".tick line").attr("stroke", this.bgcolor); + socAxis.select(".domain") + .attr("stroke", this.bgcolor) + ; + } + + getEnergyValues() { + + } } // Change the order of values in the stack function changeStack() { - wbdata.usageStackOrder = wbdata.usageStackOrder + 1; - if (wbdata.usageStackOrder > 2) { - wbdata.usageStackOrder = 0; - } - wbdata.persistGraphPreferences(); - powerGraph.updateGraph(); + wbdata.usageStackOrder = wbdata.usageStackOrder + 1; + if (wbdata.usageStackOrder > 2) { + wbdata.usageStackOrder = 0; + } + wbdata.persistGraphPreferences(); + powerGraph.updateGraph(); } var powerGraph = new PowerGraph(); From 61296aa456850177753cd12a8d6ede5812cf9af3 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Sat, 12 Nov 2022 13:16:45 +0100 Subject: [PATCH 034/201] change indentation from spaces to tabs --- web/themes/colors/processAllMqttMsg.js | 36 +- web/themes/colors/yieldmeter.js | 484 ++++++++++++------------- 2 files changed, 260 insertions(+), 260 deletions(-) diff --git a/web/themes/colors/processAllMqttMsg.js b/web/themes/colors/processAllMqttMsg.js index d2da5e4869..ec7345f3a3 100644 --- a/web/themes/colors/processAllMqttMsg.js +++ b/web/themes/colors/processAllMqttMsg.js @@ -37,7 +37,7 @@ function getIndex(topic) { function handlevar(mqttmsg, mqttpayload) { // receives all messages and calls respective function to process them - + if (mqttmsg.match(/^openwb\/graph\//i)) { processGraphMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/evu\//i)) { processEvuMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/global\/awattar\//i)) { processETProviderMessages(mqttmsg, mqttpayload); } @@ -54,27 +54,27 @@ function handlevar(mqttmsg, mqttpayload) { else if (mqttmsg.match(/^openwb\/SmartHome\/Status\//i)) { processSmartHomeDevicesStatusMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/config\/get\/sofort\/lp\//i)) { processSofortConfigMessages(mqttmsg, mqttpayload); } else if (mqttmsg.match(/^openwb\/config\/get\/pv\//i)) { processPvConfigMessages(mqttmsg, mqttpayload); } - } // end handlevar +} // end handlevar function processETProviderMessages(mqttmsg, mqttpayload) { // processes mqttmsg for topic openWB/global // called by handlevar processPreloader(mqttmsg); -// colors theme -if ( mqttmsg == 'openWB/global/ETProvider/providerName' ) { - wbdata.updateET ('etProviderName', mqttpayload); -} else if ( mqttmsg == 'openWB/global/ETProvider/modulePath' ) { - wbdata.updateET ('etModulePath', mqttpayload); -} else if ( mqttmsg == 'openWB/global/awattar/boolAwattarEnabled' ) { - wbdata.updateET('isEtEnabled' ,(mqttpayload == '1')) -} else if ( mqttmsg == 'openWB/global/awattar/pricelist' ) { - wbdata.updateET('etPriceList',mqttpayload); -} else if ( mqttmsg == 'openWB/global/awattar/MaxPriceForCharging' ) { - wbdata.updateET ('etMaxPrice', parseFloat(mqttpayload)); -} else if ( mqttmsg == 'openWB/global/awattar/ActualPriceForCharging' ) { - wbdata.updateET ('etPrice', parseFloat(mqttpayload)); -} + // colors theme + if (mqttmsg == 'openWB/global/ETProvider/providerName') { + wbdata.updateET('etProviderName', mqttpayload); + } else if (mqttmsg == 'openWB/global/ETProvider/modulePath') { + wbdata.updateET('etModulePath', mqttpayload); + } else if (mqttmsg == 'openWB/global/awattar/boolAwattarEnabled') { + wbdata.updateET('isEtEnabled', (mqttpayload == '1')) + } else if (mqttmsg == 'openWB/global/awattar/pricelist') { + wbdata.updateET('etPriceList', mqttpayload); + } else if (mqttmsg == 'openWB/global/awattar/MaxPriceForCharging') { + wbdata.updateET('etMaxPrice', parseFloat(mqttpayload)); + } else if (mqttmsg == 'openWB/global/awattar/ActualPriceForCharging') { + wbdata.updateET('etPrice', parseFloat(mqttpayload)); + } // end color theme @@ -99,7 +99,7 @@ if ( mqttmsg == 'openWB/global/ETProvider/providerName' ) { $('#navStromtarifInfo').addClass('hide'); } } - + } function processPvConfigMessages(mqttmsg, mqttpayload) { @@ -429,7 +429,7 @@ function processGlobalMessages(mqttmsg, mqttpayload) { // '3': mode stop // '4': mode standby } - else if ( mqttmsg == 'openWB/global/rfidConfigured' ) { + else if (mqttmsg == 'openWB/global/rfidConfigured') { wbdata.updateGlobal("rfidConfigured", (mqttpayload == 1)) } else if (mqttmsg == 'openWB/global/DailyYieldAllChargePointsKwh') { diff --git a/web/themes/colors/yieldmeter.js b/web/themes/colors/yieldmeter.js index 1a0cd777d9..711aea1bf7 100644 --- a/web/themes/colors/yieldmeter.js +++ b/web/themes/colors/yieldmeter.js @@ -5,268 +5,268 @@ */ class YieldMeter { - bardata; - xScale; - yScale; - svg; + bardata; + xScale; + yScale; + svg; - constructor() { - this.width = 500; - this.height = 500; - this.margin = { - top: 25, bottom: 30, left: 25, right: 0 - }; - this.labelfontsize = 16; - this.axisFontSize = 12; - this.textLength = 12; - } + constructor() { + this.width = 500; + this.height = 500; + this.margin = { + top: 25, bottom: 30, left: 25, right: 0 + }; + this.labelfontsize = 16; + this.axisFontSize = 12; + this.textLength = 12; + } - // to be called when the document is loaded - init() { - const figure = d3.select("figure#energymeter"); - this.svg = figure.append("svg") - .attr("viewBox", `0 0 500 500`); - const style = getComputedStyle(document.body); - this.houseColor = 'var(--color-house)'; - this.pvColor = 'var(--color-pv)'; - this.exportColor = 'var(--color-export)'; - this.evuColor = 'var(--color-evu)'; - this.bgColor = 'var(--color-bg)'; - this.chargeColor = 'var(--color-charging)'; - this.axisColor = 'var(--color-axis)'; - this.gridColor = 'var(--color-grid)'; - d3.select("button#energyLeftButton") - .on("click", shiftLeft) - d3.select("button#energyRightButton") - .on("click", shiftRight) - d3.select("button#calendarButton") - .on("click", toggleMonthView) - } + // to be called when the document is loaded + init() { + const figure = d3.select("figure#energymeter"); + this.svg = figure.append("svg") + .attr("viewBox", `0 0 500 500`); + const style = getComputedStyle(document.body); + this.houseColor = 'var(--color-house)'; + this.pvColor = 'var(--color-pv)'; + this.exportColor = 'var(--color-export)'; + this.evuColor = 'var(--color-evu)'; + this.bgColor = 'var(--color-bg)'; + this.chargeColor = 'var(--color-charging)'; + this.axisColor = 'var(--color-axis)'; + this.gridColor = 'var(--color-grid)'; + d3.select("button#energyLeftButton") + .on("click", shiftLeft) + d3.select("button#energyRightButton") + .on("click", shiftRight) + d3.select("button#calendarButton") + .on("click", toggleMonthView) + } - // to be called when values have changed - update() { - switch (wbdata.graphMode) { - case 'live': - this.plotdata = Object.values(wbdata.sourceSummary) - .filter((row) => (row.energy > 0)) - .concat(wbdata.usageDetails - .filter((row) => (row.energy > 0))); - break; - case 'day': - if (wbdata.showTodayGraph) { - this.plotdata = Object.values(wbdata.sourceSummary) - .filter((row) => (row.energy > 0)) - .concat(wbdata.usageDetails - .filter((row) => (row.energy > 0))); - } else { - this.plotdata = Object.values(wbdata.historicSummary) - .filter((row) => (row.energy > 0)); - } - break; - case 'month': - this.plotdata = Object.values(wbdata.historicSummary) - .filter((row) => (row.energy > 0)); - break; - default: break; - } - this.adjustLabelSize() - const svg = this.createOrUpdateSvg(); - this.drawChart(svg); - this.updateHeading(); - }; + // to be called when values have changed + update() { + switch (wbdata.graphMode) { + case 'live': + this.plotdata = Object.values(wbdata.sourceSummary) + .filter((row) => (row.energy > 0)) + .concat(wbdata.usageDetails + .filter((row) => (row.energy > 0))); + break; + case 'day': + if (wbdata.showTodayGraph) { + this.plotdata = Object.values(wbdata.sourceSummary) + .filter((row) => (row.energy > 0)) + .concat(wbdata.usageDetails + .filter((row) => (row.energy > 0))); + } else { + this.plotdata = Object.values(wbdata.historicSummary) + .filter((row) => (row.energy > 0)); + } + break; + case 'month': + this.plotdata = Object.values(wbdata.historicSummary) + .filter((row) => (row.energy > 0)); + break; + default: break; + } + this.adjustLabelSize() + const svg = this.createOrUpdateSvg(); + this.drawChart(svg); + this.updateHeading(); + }; - createOrUpdateSvg() { - this.svg.selectAll("*").remove(); - const g = this.svg.append("g") - .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); - this.xScale = d3.scaleBand() - .range([0, this.width - this.margin.left - this.margin.right]) - .padding(0.4); - this.yScale = d3.scaleLinear() - .range([this.height - this.margin.bottom - this.margin.top, 10]); - return g; - } + createOrUpdateSvg() { + this.svg.selectAll("*").remove(); + const g = this.svg.append("g") + .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); + this.xScale = d3.scaleBand() + .range([0, this.width - this.margin.left - this.margin.right]) + .padding(0.4); + this.yScale = d3.scaleLinear() + .range([this.height - this.margin.bottom - this.margin.top, 10]); + return g; + } - drawChart(svg) { - let pvcdata = this.plotdata.filter(d => (d.name == "PVCharge")) - let chargedata = this.plotdata.filter(d => d.name == "Laden") - this.plotdata = this.plotdata.filter(d => (d.name != "PVCharge")) - const ymax = d3.max(this.plotdata, (d) => d.energy); - this.xScale.domain(this.plotdata.map((d) => d.name)); - this.yScale.domain([0, Math.ceil(ymax)]); + drawChart(svg) { + let pvcdata = this.plotdata.filter(d => (d.name == "PVCharge")) + let chargedata = this.plotdata.filter(d => d.name == "Laden") + this.plotdata = this.plotdata.filter(d => (d.name != "PVCharge")) + const ymax = d3.max(this.plotdata, (d) => d.energy); + this.xScale.domain(this.plotdata.map((d) => d.name)); + this.yScale.domain([0, Math.ceil(ymax)]); - // Draw the bars - const bargroups = svg - .selectAll(".bar") - .data(this.plotdata) - .enter() - .append("g"); - bargroups - .append("rect") - .attr("class", "bar") - .attr("x", (d) => this.xScale(d.name)) - .attr("y", (d) => this.yScale(d.energy)) - .attr("width", this.xScale.bandwidth()) - .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) - .attr("fill", (d) => d.color); + // Draw the bars + const bargroups = svg + .selectAll(".bar") + .data(this.plotdata) + .enter() + .append("g"); + bargroups + .append("rect") + .attr("class", "bar") + .attr("x", (d) => this.xScale(d.name)) + .attr("y", (d) => this.yScale(d.energy)) + .attr("width", this.xScale.bandwidth()) + .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) + .attr("fill", (d) => d.color); - // Display the PV Charging inner bar - if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { - const pvcBargroup = svg - .selectAll(".pvcBar") - .data(pvcdata) - .enter() - .append("g") - pvcBargroup - .append("rect") - .attr("class", "bar") - .attr("x", (d) => this.xScale("Laden") + this.xScale.bandwidth() / 6) - .attr("y", (d) => this.yScale(d.energy)) - .attr("width", this.xScale.bandwidth() * 2 / 3) - .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) - .attr("fill", this.pvColor) - .attr("fill-opacity", "66%"); - } - const yAxisGenerator = d3.axisLeft(this.yScale) - .tickFormat(function (d) { - return ((d > 0) ? d : ""); - }) - .ticks(8) - .tickSizeInner(-this.width); + // Display the PV Charging inner bar + if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { + const pvcBargroup = svg + .selectAll(".pvcBar") + .data(pvcdata) + .enter() + .append("g") + pvcBargroup + .append("rect") + .attr("class", "bar") + .attr("x", (d) => this.xScale("Laden") + this.xScale.bandwidth() / 6) + .attr("y", (d) => this.yScale(d.energy)) + .attr("width", this.xScale.bandwidth() * 2 / 3) + .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) + .attr("fill", this.pvColor) + .attr("fill-opacity", "66%"); + } + const yAxisGenerator = d3.axisLeft(this.yScale) + .tickFormat(function (d) { + return ((d > 0) ? d : ""); + }) + .ticks(8) + .tickSizeInner(-this.width); - const yAxis = svg.append("g") - .attr("class", "axis") - .attr("transform", "translate(0," + 0 + ")") - .call(yAxisGenerator); + const yAxis = svg.append("g") + .attr("class", "axis") + .attr("transform", "translate(0," + 0 + ")") + .call(yAxisGenerator); - yAxis.append("text") - .attr("y", 6) - .attr("dy", "0.71em") - .attr("text-anchor", "end") - .text("energy"); + yAxis.append("text") + .attr("y", 6) + .attr("dy", "0.71em") + .attr("text-anchor", "end") + .text("energy"); - yAxis.selectAll(".tick").attr("font-size", this.axisFontSize); - if (wbdata.showGrid) { - yAxis.selectAll(".tick line") - .attr("stroke", this.gridColor) - .attr("stroke-width", "0.5"); - } else { - yAxis.selectAll(".tick line").attr("stroke", this.bgColor); - } - yAxis.select(".domain") - .attr("stroke", this.bgcolor); + yAxis.selectAll(".tick").attr("font-size", this.axisFontSize); + if (wbdata.showGrid) { + yAxis.selectAll(".tick line") + .attr("stroke", this.gridColor) + .attr("stroke-width", "0.5"); + } else { + yAxis.selectAll(".tick line").attr("stroke", this.bgColor); + } + yAxis.select(".domain") + .attr("stroke", this.bgcolor); - svg.append("text") - .attr("x", -this.margin.left) - .attr("y", -15) - .style("fill", this.axisColor) - .attr("font-size", this.axisFontSize) - .text("kWh") - ; + svg.append("text") + .attr("x", -this.margin.left) + .attr("y", -15) + .style("fill", this.axisColor) + .attr("font-size", this.axisFontSize) + .text("kWh") + ; - // add value labels to the bars - const labels = svg.selectAll(".label") - .data(this.plotdata) - .enter() - .append("g"); - labels - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", (d) => { - if (d.name == "Laden" && pvcdata.length > 0) { - return this.yScale(d.energy) - 25 - } else { - return this.yScale(d.energy) - 10 - } - }) - .attr("font-size", this.labelfontsize) - .attr("text-anchor", "middle") - .attr("fill", (d) => d.color) - .text((d) => (formatWattH(d.energy * 1000))); + // add value labels to the bars + const labels = svg.selectAll(".label") + .data(this.plotdata) + .enter() + .append("g"); + labels + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", (d) => { + if (d.name == "Laden" && pvcdata.length > 0) { + return this.yScale(d.energy) - 25 + } else { + return this.yScale(d.energy) - 10 + } + }) + .attr("font-size", this.labelfontsize) + .attr("text-anchor", "middle") + .attr("fill", (d) => d.color) + .text((d) => (formatWattH(d.energy * 1000))); - // add a PV percentage tag to the charging bar - if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { - let pvRatio = Math.round (pvcdata[0].energy / chargedata[0].energy * 100) + // add a PV percentage tag to the charging bar + if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { + let pvRatio = Math.round(pvcdata[0].energy / chargedata[0].energy * 100) - const pvtags = svg.selectAll(".pvtag") - .data(chargedata) - .enter() - .append("g"); - pvtags - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", (d) => this.yScale(d.energy) - 10) - .attr("font-size", this.labelfontsize - 2) - .attr("text-anchor", "middle") - .attr("fill", (d) => this.pvColor) - .text((d) => ("(PV: " + pvRatio.toLocaleString(undefined) + " %)")); - } + const pvtags = svg.selectAll(".pvtag") + .data(chargedata) + .enter() + .append("g"); + pvtags + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", (d) => this.yScale(d.energy) - 10) + .attr("font-size", this.labelfontsize - 2) + .attr("text-anchor", "middle") + .attr("fill", (d) => this.pvColor) + .text((d) => ("(PV: " + pvRatio.toLocaleString(undefined) + " %)")); + } - // Add category labels - labels - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", this.height - this.margin.bottom - 5) - .attr("font-size", this.labelfontsize) - .attr("text-anchor", "middle") - .attr("fill", (d) => d.color) - .text((d) => (this.truncateCategory(d.name))); + // Add category labels + labels + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", this.height - this.margin.bottom - 5) + .attr("font-size", this.labelfontsize) + .attr("text-anchor", "middle") + .attr("fill", (d) => d.color) + .text((d) => (this.truncateCategory(d.name))); - } + } - updateHeading() { - var heading = "Energie "; + updateHeading() { + var heading = "Energie "; - switch (wbdata.graphMode) { - case 'live': - heading = heading + " heute"; - break; - case 'day': - if (wbdata.showTodayGraph) { - heading = heading + " heute"; - } else { - heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; - } - break; - case 'month': - heading = "Monatswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); - break; - default: break; - } - d3.select("h3#energyheading").text(heading); - } + switch (wbdata.graphMode) { + case 'live': + heading = heading + " heute"; + break; + case 'day': + if (wbdata.showTodayGraph) { + heading = heading + " heute"; + } else { + heading = heading + wbdata.graphDate.getDate() + "." + (wbdata.graphDate.getMonth() + 1) + "."; + } + break; + case 'month': + heading = "Monatswerte " + formatMonth(wbdata.graphMonth.month, wbdata.graphMonth.year); + break; + default: break; + } + d3.select("h3#energyheading").text(heading); + } - adjustLabelSize() { - let xCount = this.plotdata.length - if (xCount <= 5) { - this.maxTextLength = 12; - this.labelfontsize = 16 - } else if (xCount == 6) { - this.maxTextLength = 11; - this.labelfontsize = 14 - } else if (xCount > 6 && xCount <= 8) { - this.maxTextLength = 8; - this.labelfontsize = 13 - } else if (xCount == 9) { - this.maxTextLength = 8; - this.labelfontsize = 11; - } else if (xCount == 10) { - this.maxTextLength = 7; - this.labelfontsize = 10; - } - else { - this.maxTextLength = 6; - this.labelfontsize = 9 - } - } + adjustLabelSize() { + let xCount = this.plotdata.length + if (xCount <= 5) { + this.maxTextLength = 12; + this.labelfontsize = 16 + } else if (xCount == 6) { + this.maxTextLength = 11; + this.labelfontsize = 14 + } else if (xCount > 6 && xCount <= 8) { + this.maxTextLength = 8; + this.labelfontsize = 13 + } else if (xCount == 9) { + this.maxTextLength = 8; + this.labelfontsize = 11; + } else if (xCount == 10) { + this.maxTextLength = 7; + this.labelfontsize = 10; + } + else { + this.maxTextLength = 6; + this.labelfontsize = 9 + } + } - truncateCategory(name) { - if (name.length > this.maxTextLength) { - return name.substr(0, this.maxTextLength) + "." - } else { - return name - } - } + truncateCategory(name) { + if (name.length > this.maxTextLength) { + return name.substr(0, this.maxTextLength) + "." + } else { + return name + } + } } From 9058f82747ac6c21c0739ec6bdd1f438312a4dfe Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 13 Nov 2022 16:56:25 +0100 Subject: [PATCH 035/201] Add VIN selector to SOC PSA --- modules/soc_psa/main.sh | 8 +++++--- modules/soc_psa/psasoc.py | 14 ++++++++++++-- runs/updateConfig.sh | 6 ++++++ web/settings/modulconfiglp.php | 12 ++++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/modules/soc_psa/main.sh b/modules/soc_psa/main.sh index 76e10afb45..2626a0ef93 100755 --- a/modules/soc_psa/main.sh +++ b/modules/soc_psa/main.sh @@ -34,6 +34,7 @@ case $CHARGEPOINT in password=$psa_passlp2 clientId=$psa_clientidlp2 clientSecret=$psa_clientsecretlp2 + vehicleId=$psa_vinlp2 soccalc=$psa_soccalclp2 manufacturer=$psa_manufacturerlp2 socOnlineIntervall=$psa_intervallp2 @@ -59,6 +60,7 @@ case $CHARGEPOINT in password=$psa_passlp1 clientId=$psa_clientidlp1 clientSecret=$psa_clientsecretlp1 + vehicleId=$psa_vinlp1 soccalc=$psa_soccalclp1 manufacturer=$psa_manufacturerlp1 socOnlineIntervall=$psa_intervallp1 @@ -100,7 +102,7 @@ if (($soccalc == 0)); then #manual calculation not enabled, using existing logic incrementTimer else echo 0 > $soctimerfile - sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc + sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc $vehicleId if [[ $? == 0 ]]; then openwbDebugLog ${DMOD} 0 "Lp$CHARGEPOINT: Fetched from $manufacturer: $(<$psaSocFile)%" else @@ -113,7 +115,7 @@ else # manual calculation enabled, combining PSA module with manual calc method openwbDebugLog ${DMOD} 0 "Lp$CHARGEPOINT: Charging started. Fetching SoC from $manufacturer out of order." soctimer=0 echo 0 > $soctimerfile - sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc + sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc $vehicleId if [[ $? == 0 ]]; then echo $(<$psaSocFile) > $socFile echo $(<$psaSocFile) > $manualSocFile @@ -132,7 +134,7 @@ else # manual calculation enabled, combining PSA module with manual calc method else openwbDebugLog ${DMOD} 0 "Lp$CHARGEPOINT: Fetching SoC from $manufacturer" echo 0 > $soctimerfile - sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc + sudo python $MODULEDIR/psasoc.py $CHARGEPOINT $username $password $clientId $clientSecret $manufacturer $soccalc $vehicleId if [[ $? == 0 ]]; then # if fetched SoC is equal from last used fetched SoC and car is plugged in if [[ $(<$psaSocFile) == $(<$psaSocFile_last) ]] && [[ $(($(<$plugstat))) == 1 ]]; then diff --git a/modules/soc_psa/psasoc.py b/modules/soc_psa/psasoc.py index c4d489d84b..92d2399994 100755 --- a/modules/soc_psa/psasoc.py +++ b/modules/soc_psa/psasoc.py @@ -34,6 +34,7 @@ client_secret=str(sys.argv[5]) manufacturer=str(sys.argv[6]) soccalc=str(sys.argv[7]) +vehicleId=str(sys.argv[8]) named_tuple = time.localtime() # get struct_time time_string = time.strftime("%m/%d/%Y, %H:%M:%S psasoc lp"+chargepoint, named_tuple) @@ -93,8 +94,17 @@ f.write(str(responestatus)) f.close() vin_list = json.loads(responsetext) -vin_id = vin_list ['_embedded']['vehicles'][0]['id'] -vin_vin = vin_list ['_embedded']['vehicles'][0]['vin'] + +# search for entry of entered VIN +vin_selected = filter(lambda x: x['vin'] == vehicleId, vin_list['_embedded']['vehicles']) +vin_id = vin_selected [0]['id'] +vin_vin = vin_selected [0]['vin'] + +f = open('/var/www/html/openWB/ramdisk/psareply2filterVINlp'+chargepoint, 'w') +f.write('VIN: ' + str(vin_vin)) +f.write(', ID: ' + str(vin_id)) +f.close() + payload = {'client_id':client_id} data = urllib.urlencode(payload) data = data.encode('Big5') diff --git a/runs/updateConfig.sh b/runs/updateConfig.sh index 8ae746a3dc..a7218e2037 100755 --- a/runs/updateConfig.sh +++ b/runs/updateConfig.sh @@ -2063,6 +2063,12 @@ updateConfig(){ if ! grep -Fq "psa_manufacturerlp2=" $ConfigFile; then echo "psa_manufacturerlp2=Peugeot" >> $ConfigFile fi + if ! grep -Fq "psa_vinlp1=" $ConfigFile; then + echo "psa_vinlp1=''" >> $ConfigFile + fi + if ! grep -Fq "psa_vinlp2=" $ConfigFile; then + echo "psa_vinlp2=''" >> $ConfigFile + fi if ! grep -Fq "soc_eq_client_id_lp1=" $ConfigFile; then { echo "soc_eq_client_id_lp1=ID" diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index 7a1cd78467..3513df12cf 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -1840,6 +1840,12 @@ function visibility_i3_soccalclp1() {
+
+ +
+ +
+
@@ -3532,6 +3538,12 @@ function visibility_i3_soccalclp2() {
+
+ +
+ +
+
From beb60656d749fb3e26398626badcebaa15700c08 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 13 Nov 2022 20:37:43 +0100 Subject: [PATCH 036/201] Change SoC PSA VIN to optional --- modules/soc_psa/psasoc.py | 20 +++++++++++++++----- web/settings/modulconfiglp.php | 6 ++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/modules/soc_psa/psasoc.py b/modules/soc_psa/psasoc.py index 92d2399994..0f096f6271 100755 --- a/modules/soc_psa/psasoc.py +++ b/modules/soc_psa/psasoc.py @@ -34,7 +34,11 @@ client_secret=str(sys.argv[5]) manufacturer=str(sys.argv[6]) soccalc=str(sys.argv[7]) -vehicleId=str(sys.argv[8]) + +# VehicleId is optional +vehicleId = False +if (len(sys.argv) > 8 ): + vehicleId=str(sys.argv[8]) named_tuple = time.localtime() # get struct_time time_string = time.strftime("%m/%d/%Y, %H:%M:%S psasoc lp"+chargepoint, named_tuple) @@ -95,10 +99,16 @@ f.close() vin_list = json.loads(responsetext) -# search for entry of entered VIN -vin_selected = filter(lambda x: x['vin'] == vehicleId, vin_list['_embedded']['vehicles']) -vin_id = vin_selected [0]['id'] -vin_vin = vin_selected [0]['vin'] +# if VIN is not given, first car will be selected: +if not vehicleId: + vin_id = vin_list ['_embedded']['vehicles'][0]['id'] + vin_vin = vin_list ['_embedded']['vehicles'][0]['vin'] + +else: + # search for entry of given VIN + vin_selected = filter(lambda x: x['vin'] == vehicleId, vin_list['_embedded']['vehicles']) + vin_id = vin_selected [0]['id'] + vin_vin = vin_selected [0]['vin'] f = open('/var/www/html/openWB/ramdisk/psareply2filterVINlp'+chargepoint, 'w') f.write('VIN: ' + str(vin_vin)) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index 3513df12cf..1cbcab7d31 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -1844,6 +1844,9 @@ function visibility_i3_soccalclp1() {
+ + VIN des gewünschten Fahrzeugs. Wird keine VIN angegeben, wird das erste Fahrzeug aus dem Account verwendet. +
@@ -3542,6 +3545,9 @@ function visibility_i3_soccalclp2() {
+ + VIN des gewünschten Fahrzeugs. Wird keine VIN angegeben, wird das erste Fahrzeug aus dem Account verwendet. +
From 334b3b5c01ac627d7d0034849ce50f1778b91512 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Mon, 14 Nov 2022 14:25:35 +0100 Subject: [PATCH 037/201] Nissan soc module works for all Nissan vehicles (#2488) --- web/settings/modulconfiglp.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/settings/modulconfiglp.php b/web/settings/modulconfiglp.php index 7a1cd78467..51d4507376 100644 --- a/web/settings/modulconfiglp.php +++ b/web/settings/modulconfiglp.php @@ -735,7 +735,7 @@ function visibility_twcmanagerlp1_connection() { - + @@ -2932,7 +2932,7 @@ function visibility_twcmanagerlp2_connection() { - + From d04c8e24081d324dc143a34705290a62e4d60206 Mon Sep 17 00:00:00 2001 From: okaegi <72255431+okaegi@users.noreply.github.com> Date: Mon, 14 Nov 2022 14:27:22 +0100 Subject: [PATCH 038/201] Python Log im Smarthome... diverse Korrekutren (#2493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HTTP Gerät: Es wurde auf Python Log umgestellt. Es wurde im on / off trigger ein Browser Header genommen. Es wurde auf static Types umgestellt. Dac Gerät: Es wurde auf Python Log umgestellt. Neuer Dac typ M120T von Pigeon Es wurde auf static Types umgestellt. Ratiotherm WP Es wurde auf Python Log umgestellt. Es wurde auf static Types umgestellt. Stiebel WP Es wurde auf Python Log umgestellt. Es wurde auf static Types umgestellt. Smarthome Config 2.0 Maximaler Speicherladung 30'000 Textkorrektur bei DAC J/N Hausverbrauch besser erklärt --- modules/smarthome/http/off.py | 36 +++++++---------- modules/smarthome/http/on.py | 36 +++++++---------- modules/smarthome/http/watt.py | 58 +++++++++++----------------- modules/smarthome/nxdacxx/off.py | 26 ++++++------- modules/smarthome/nxdacxx/on.py | 27 +++++++------ modules/smarthome/nxdacxx/watt.py | 47 +++++++++++----------- modules/smarthome/ratiotherm/off.py | 4 +- modules/smarthome/ratiotherm/on.py | 4 +- modules/smarthome/ratiotherm/watt.py | 14 +++---- modules/smarthome/stiebel/off.py | 38 ++++++------------ modules/smarthome/stiebel/on.py | 40 ++++++------------- modules/smarthome/stiebel/watt.py | 34 +++++++--------- packages/smarthome/smartlog.py | 2 +- packages/smarthome/smartret.py | 8 ++++ runs/mqttsub.py | 2 +- runs/usmarthome/smartnxdacxx.py | 17 ++++---- web/settings/smarthomeconfig.php | 17 ++++---- 17 files changed, 174 insertions(+), 236 deletions(-) create mode 100644 packages/smarthome/smartret.py diff --git a/modules/smarthome/http/off.py b/modules/smarthome/http/off.py index ff3d8b4751..190a972842 100644 --- a/modules/smarthome/http/off.py +++ b/modules/smarthome/http/off.py @@ -1,27 +1,17 @@ #!/usr/bin/python3 import sys -import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii -import urllib.request +import logging +from smarthome.smartlog import initlog +from urllib.request import Request, urlopen from urllib.parse import urlparse -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S http off.py", named_tuple) -devicenumber=str(sys.argv[1]) -uberschuss=int(sys.argv[3]) -url=str(sys.argv[4]) +devicenumber = int(sys.argv[1]) +uberschuss = int(sys.argv[3]) +url = str(sys.argv[4]) +initlog("http", devicenumber) +log = logging.getLogger("http") if not urlparse(url).scheme: - url = 'http://' + url -file_string= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_http.log' -if os.path.isfile(file_string): - f = open( file_string , 'a') -else: - f = open( file_string , 'w') -print ('%s devicenr %s url %s)' % (time_string,devicenumber,url),file=f) -f.close() -urllib.request.urlopen(url, timeout=5) \ No newline at end of file + url = 'http://' + url +log.info('off devicenr %d url %s' % (devicenumber, url)) +headers = {'User-Agent': 'Mozilla/5.0'} +request = Request(url, headers=headers) +urlopen(request, timeout=5).read() diff --git a/modules/smarthome/http/on.py b/modules/smarthome/http/on.py index 8888301446..9eb6cde138 100644 --- a/modules/smarthome/http/on.py +++ b/modules/smarthome/http/on.py @@ -1,27 +1,17 @@ #!/usr/bin/python3 import sys -import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii -import urllib.request +import logging +from smarthome.smartlog import initlog +from urllib.request import Request, urlopen from urllib.parse import urlparse -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S http on.py", named_tuple) -devicenumber=str(sys.argv[1]) -uberschuss=int(sys.argv[3]) -url=str(sys.argv[4]) +devicenumber = int(sys.argv[1]) +uberschuss = int(sys.argv[3]) +url = str(sys.argv[4]) +initlog("http", devicenumber) +log = logging.getLogger("http") if not urlparse(url).scheme: - url = 'http://' + url -file_string= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_http.log' -if os.path.isfile(file_string): - f = open( file_string , 'a') -else: - f = open( file_string , 'w') -print ('%s devicenr %s url %s' % (time_string,devicenumber,url),file=f) -f.close() -urllib.request.urlopen(url, timeout=5) + url = 'http://' + url +log.info('on devicenr %d url %s' % (devicenumber, url)) +headers = {'User-Agent': 'Mozilla/5.0'} +request = Request(url, headers=headers) +urlopen(request, timeout=5).read() diff --git a/modules/smarthome/http/watt.py b/modules/smarthome/http/watt.py index 0dc45cf701..fa04b37eab 100755 --- a/modules/smarthome/http/watt.py +++ b/modules/smarthome/http/watt.py @@ -1,57 +1,47 @@ #!/usr/bin/python3 import sys -import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii +import logging +from smarthome.smartlog import initlog +from smarthome.smartret import writeret import urllib.request from urllib.parse import urlparse -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S http watt.py", named_tuple) -devicenumber=str(sys.argv[1]) -uberschuss=int(sys.argv[3]) -url=str(sys.argv[4]) +devicenumber = int(sys.argv[1]) +uberschuss = int(sys.argv[3]) +url = str(sys.argv[4]) try: - urlc=str(sys.argv[5]) -except: + urlc = str(sys.argv[5]) +except Exception: urlc = "none" try: - urlstate=str(sys.argv[8]) -except: + urlstate = str(sys.argv[8]) +except Exception: urlstate = "none" +initlog("http", devicenumber) +log = logging.getLogger("http") if not urlparse(url).scheme: - url = 'http://' + url + url = 'http://' + url if not urlparse(urlstate).scheme and not urlstate.startswith("none"): - urlstate = 'http://' + urlstate + urlstate = 'http://' + urlstate if uberschuss < 0: - uberschuss = 0 -urlrep= url.replace("", str(uberschuss)) -file_string= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_http.log' -if os.path.isfile(file_string): - f = open( file_string , 'a') -else: - f = open( file_string , 'w') -print ('%s devicenr %s orig url %s replaced url %s urlc %s urlstate %s'% (time_string,devicenumber,url,urlrep,urlc,urlstate),file=f) + uberschuss = 0 +urlrep = url.replace("", str(uberschuss)) +log.info('watt devicenr %d orig url %s replaced url %s urlc %s urlstate %s' % + (devicenumber, url, urlrep, urlc, urlstate)) if not urlstate.startswith("none"): stateurl_response = 0 try: stateurl_response = urllib.request.urlopen(urlstate, timeout=5).read().decode("utf-8") except urllib.error.HTTPError as e: - print('%s StateURL HTTP Error: %d'%(time_string,e.code),file=f) + log.info('watt StateURL HTTP Error: %d' % (e.code)) except urllib.error.URLError as e: - print('%s StateURL URL Error: %s'%(time_string,e.reason),file=f) + log.info('watt StateURL URL Error: %s' % (e.reason)) try: state = int(stateurl_response) except ValueError: - print ('%s StateURL delivered no integer but: %s'%(time_string,stateurl_response),file=f) + log.info('watt StateURL delivered no integer but: %s' % (stateurl_response)) state = 0 else: state = 0 -f.close() aktpowerfl = float(urllib.request.urlopen(urlrep, timeout=5).read().decode("utf-8")) aktpower = int(aktpowerfl) if state == 1 or aktpower > 50: @@ -65,7 +55,5 @@ urlc = 'http://' + urlc powercfl = float(urllib.request.urlopen(urlc, timeout=5).read().decode("utf-8")) powerc = int(powercfl) -answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) + ',"on":' + str(relais) + '} ' -f1 = open('/var/www/html/openWB/ramdisk/smarthome_device_ret' + str(devicenumber), 'w') -json.dump(answer,f1) -f1.close() +answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) + ',"on":' + str(relais) + '}' +writeret(answer, devicenumber) diff --git a/modules/smarthome/nxdacxx/off.py b/modules/smarthome/nxdacxx/off.py index 89d4be1788..dbdf29ceb8 100644 --- a/modules/smarthome/nxdacxx/off.py +++ b/modules/smarthome/nxdacxx/off.py @@ -1,25 +1,25 @@ #!/usr/bin/python3 import sys import os -import time -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S N4DAC02 off.py", named_tuple) -devicenumber = str(sys.argv[1]) +import logging +from smarthome.smartlog import initlog +from pymodbus.client.sync import ModbusTcpClient +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) +port = int(sys.argv[4]) +dactyp = int(sys.argv[5]) bp = '/var/www/html/openWB/ramdisk/smarthome_device_' -file_string = bp + str(devicenumber) + '_N4DAC02.log' file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' file_stringcount5 = bp + str(devicenumber) + '_count5' -if os.path.isfile(file_string): - pass -else: - with open(file_string, 'w') as f: - print('N4DAC02 start log', file=f) -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s' - % (time_string, devicenumber, ipadr), file=f) +initlog("DAC", devicenumber) +log = logging.getLogger("DAC") +log.info('off devicenr %d ipadr %s dactyp %d' % (devicenumber, ipadr, dactyp)) +if dactyp == 2: + client = ModbusTcpClient(ipadr, port=port) + # DO1 ausschalten um SGready zu sperren + rq = client.write_coil(0, False) pvmodus = 0 if os.path.isfile(file_stringpv): with open(file_stringpv, 'r') as f: diff --git a/modules/smarthome/nxdacxx/on.py b/modules/smarthome/nxdacxx/on.py index 7917e61fb9..413ff9f047 100644 --- a/modules/smarthome/nxdacxx/on.py +++ b/modules/smarthome/nxdacxx/on.py @@ -1,25 +1,24 @@ #!/usr/bin/python3 import sys -import os -import time -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S N4DAC02 on.py", named_tuple) -devicenumber = str(sys.argv[1]) +import logging +from smarthome.smartlog import initlog +from pymodbus.client.sync import ModbusTcpClient +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) +port = int(sys.argv[4]) +dactyp = int(sys.argv[5]) +initlog("DAC", devicenumber) +log = logging.getLogger("DAC") bp = '/var/www/html/openWB/ramdisk/smarthome_device_' -file_string = bp + str(devicenumber) + '_N4DAC02.log' file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' file_stringcount5 = bp + str(devicenumber) + '_count5' -if os.path.isfile(file_string): - pass -else: - with open(file_string, 'w') as f: - print('N4DAC02 start log', file=f) -with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s' % - (time_string, devicenumber, ipadr), file=f) +log.info('on devicenr %d ipadr %s dactyp %d' % (devicenumber, ipadr, dactyp)) +if dactyp == 2: + client = ModbusTcpClient(ipadr, port=port) + # DO1 einschalten um SGready zu aktivieren + rq = client.write_coil(0, True) pvmodus = 1 with open(file_stringpv, 'w') as f: f.write(str(pvmodus)) diff --git a/modules/smarthome/nxdacxx/watt.py b/modules/smarthome/nxdacxx/watt.py index 9231eb2656..cf98e89f79 100644 --- a/modules/smarthome/nxdacxx/watt.py +++ b/modules/smarthome/nxdacxx/watt.py @@ -1,23 +1,23 @@ #!/usr/bin/python3 import sys import os -import time -import json from pymodbus.client.sync import ModbusTcpClient -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S N4DAC02 watty.py", named_tuple) -devicenumber = str(sys.argv[1]) +import logging +from smarthome.smartlog import initlog +from smarthome.smartret import writeret +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) maxpower = int(sys.argv[4]) forcesend = int(sys.argv[5]) port = int(sys.argv[6]) dactyp = int(sys.argv[7]) -# forcesend = 0 default acthor time period applies +initlog("DAC", devicenumber) +log = logging.getLogger("DAC") +# forcesend = 0 default time period applies # forcesend = 1 default overwritten send now # forcesend = 9 default overwritten no send bp = '/var/www/html/openWB/ramdisk/smarthome_device_' -file_string = bp + str(devicenumber) + '_N4DAC02.log' file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' file_stringcount5 = bp + str(devicenumber) + '_count5' @@ -71,16 +71,10 @@ if count1 > 80: count1 = 0 if count1 < 3: - if os.path.isfile(file_string): - pass - else: - with open(file_string, 'w') as f: - print('N4DAC02 start log', file=f) - with open(file_string, 'a') as f: - helpstr = '%s devicenr %s ipadr %s ueberschuss %6d port %4d' - helpstr += ' maxueberschuss %6d pvmodus %1d modbuswrite %1d' - print(helpstr % (time_string, devicenumber, ipadr, uberschuss, - port, maxpower, pvmodus, modbuswrite), file=f) + helpstr = 'devicenr %d ipadr %s ueberschuss %6d port %4d' + helpstr += ' maxueberschuss %6d pvmodus %1d modbuswrite %1d' + log.info(helpstr % (devicenumber, ipadr, uberschuss, + port, maxpower, pvmodus, modbuswrite)) # modbus write if modbuswrite == 1: client = ModbusTcpClient(ipadr, port=port) @@ -88,14 +82,21 @@ # 10 Volts are 1000 volt = int((neupower * 1000) / maxpower) rq = client.write_register(1, volt, unit=1) - else: + elif dactyp == 1: # 10 volts are 4000 volt = int((neupower * 4000) / maxpower) rq = client.write_register(0x01f4, volt, unit=1) + elif dactyp == 2: + volt = int((neupower * 4095) / maxpower) + if volt < 370: + volt = 370 + # ausgabe nicht kleiner 0,9V sonst Leistungsregelung der WP aus + rq = client.write_register(0, volt, unit=1) + else: + pass if count1 < 3: - with open(file_string, 'a') as f: - print('%s devicenr %s ipadr %s Volt %6d dactyp %d written by modbus ' % - (time_string, devicenumber, ipadr, volt, dactyp), file=f) + log.info('devicenr %d ipadr %s Volt %6d dactyp %d written by modbus ' % + (devicenumber, ipadr, volt, dactyp)) with open(file_stringcount, 'w') as f: f.write(str(count1)) else: @@ -104,6 +105,4 @@ answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) answer += ',"send":' + str(modbuswrite) + ',"sendpower":' + str(volt) answer += ',"on":' + str(pvmodus) + '}' -with open('/var/www/html/openWB/ramdisk/smarthome_device_ret' + - str(devicenumber), 'w') as f1: - json.dump(answer, f1) +writeret(answer, devicenumber) diff --git a/modules/smarthome/ratiotherm/off.py b/modules/smarthome/ratiotherm/off.py index 677503eb2b..118f326f40 100644 --- a/modules/smarthome/ratiotherm/off.py +++ b/modules/smarthome/ratiotherm/off.py @@ -3,7 +3,7 @@ import sys import logging from smarthome.smartlog import initlog -devicenumber = str(sys.argv[1]) +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) bp = '/var/www/html/openWB/ramdisk/smarthome_device_' @@ -13,7 +13,7 @@ initlog("ratiotherm", devicenumber) log = logging.getLogger("ratiotherm") aktpower = 0 -log.info(" off devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" +log.info(" off devicenr %d ipadr %s ueberschuss %6d Akt Leistung %6d" % (devicenumber, ipadr, uberschuss, aktpower)) pvmodus = 0 if os.path.isfile(file_stringpv): diff --git a/modules/smarthome/ratiotherm/on.py b/modules/smarthome/ratiotherm/on.py index 568eec1c30..5368dedc28 100644 --- a/modules/smarthome/ratiotherm/on.py +++ b/modules/smarthome/ratiotherm/on.py @@ -2,7 +2,7 @@ import sys import logging from smarthome.smartlog import initlog -devicenumber = str(sys.argv[1]) +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) bp = '/var/www/html/openWB/ramdisk/smarthome_device_' @@ -14,7 +14,7 @@ file_stringpv = bp + str(devicenumber) + '_pv' file_stringcount = bp + str(devicenumber) + '_count' aktpower = 0 -log.info(" on devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" +log.info(" on devicenr %d ipadr %s ueberschuss %6d Akt Leistung %6d" % (devicenumber, ipadr, uberschuss, aktpower)) with open(file_stringpv, 'w') as f: f.write(str(1)) diff --git a/modules/smarthome/ratiotherm/watt.py b/modules/smarthome/ratiotherm/watt.py index 8dce8efa36..bcc0b5ec95 100644 --- a/modules/smarthome/ratiotherm/watt.py +++ b/modules/smarthome/ratiotherm/watt.py @@ -1,12 +1,12 @@ #!/usr/bin/python3 import sys import os -import json from pymodbus.payload import BinaryPayloadBuilder, Endian from pymodbus.client.sync import ModbusTcpClient import logging from smarthome.smartlog import initlog -devicenumber = str(sys.argv[1]) +from smarthome.smartret import writeret +devicenumber = int(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) forcesend = int(sys.argv[4]) @@ -70,9 +70,9 @@ with open(file_stringpv, 'w') as f: f.write(str(pvmodus)) if count1 < 3: - log.info(" watt devicenr %s ipadr %s ueberschuss %6d Akt Leistung %6d" + log.info(" watt devicenr %d ipadr %s ueberschuss %6d Akt Leistung %6d" % (devicenumber, ipadr, uberschuss, aktpower)) - log.info(" watt devicenr %s ipadr %s neupower %6d pvmodus %1d modbusw %1d" + log.info(" watt devicenr %d ipadr %s neupower %6d pvmodus %1d modbusw %1d" % (devicenumber, ipadr, neupower, pvmodus, modbuswrite)) # modbus write if modbuswrite == 1: @@ -84,7 +84,7 @@ client = ModbusTcpClient(ipadr, port=502) client.write_register(100, pay[0], unit=1) if count1 < 3: - log.info(" watt devicenr %s ipadr %s written %6d %#4X" + log.info(" watt devicenr %d ipadr %s written %6d %#4X" % (devicenumber, ipadr, pay[0], pay[0])) else: if pvmodus == 99: @@ -92,6 +92,4 @@ answer = '{"power":' + str(aktpower) + ',"powerc":0' answer += ',"send":' + str(modbuswrite) + ',"sendpower":' + str(neupower) answer += ',"on":' + str(pvmodus) + '}' -with open('/var/www/html/openWB/ramdisk/smarthome_device_ret' + - str(devicenumber), 'w') as f1: - json.dump(answer, f1) +writeret(answer, devicenumber) diff --git a/modules/smarthome/stiebel/off.py b/modules/smarthome/stiebel/off.py index f80f3a24d9..d6d86cb0f0 100644 --- a/modules/smarthome/stiebel/off.py +++ b/modules/smarthome/stiebel/off.py @@ -1,33 +1,19 @@ #!/usr/bin/python3 import sys -import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii from pymodbus.client.sync import ModbusTcpClient -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S stiebel off.py", named_tuple) -devicenumber=str(sys.argv[1]) -ipadr=str(sys.argv[2]) -uberschuss=int(sys.argv[3]) -# standard -file_string= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_stiebel.log' -file_stringpv= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' -if os.path.isfile(file_string): - f = open( file_string , 'a') -else: - f = open( file_string , 'w') -print ('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' % (time_string,devicenumber,ipadr,uberschuss),file=f) +import logging +from smarthome.smartlog import initlog +devicenumber = int(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +initlog("stiebel", devicenumber) +log = logging.getLogger("stiebel") +file_stringpv = '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' +log.info('off devicenr %d ipadr %s ueberschuss %6d try to connect (modbus)' % (devicenumber, ipadr, uberschuss)) client = ModbusTcpClient(ipadr, port=502) # deactivate switch one (manual 4002) rq = client.write_register(4001, 0, unit=1) -print ('%s devicenr %s ipadr %s ' % (time_string,devicenumber,ipadr),file=f) -f.close() +log.info('off devicenr %d ipadr %s ' % (devicenumber, ipadr)) pvmodus = 0 -f = open( file_stringpv , 'w') -f.write(str(pvmodus)) -f.close() \ No newline at end of file +with open(file_stringpv, 'w') as f: + f.write(str(pvmodus)) diff --git a/modules/smarthome/stiebel/on.py b/modules/smarthome/stiebel/on.py index 1e3a969d91..c363a66220 100644 --- a/modules/smarthome/stiebel/on.py +++ b/modules/smarthome/stiebel/on.py @@ -1,34 +1,18 @@ #!/usr/bin/python3 import sys -import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii from pymodbus.client.sync import ModbusTcpClient -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S stiebel on.py", named_tuple) -devicenumber=str(sys.argv[1]) -ipadr=str(sys.argv[2]) -uberschuss=int(sys.argv[3]) -# standard -# lesen -# own log -file_string= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_stiebel.log' -file_stringpv= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' -if os.path.isfile(file_string): - f = open( file_string , 'a') -else: - f = open( file_string , 'w') -print ('%s devicenr %s ipadr %s ueberschuss %6d try to connect (modbus)' % (time_string,devicenumber,ipadr,uberschuss),file=f) +import logging +from smarthome.smartlog import initlog +devicenumber = int(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +initlog("stiebel", devicenumber) +log = logging.getLogger("stiebel") +file_stringpv = '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' +log.info('on devicenr %d ipadr %s ueberschuss %6d try to connect (modbus)' % (devicenumber, ipadr, uberschuss)) client = ModbusTcpClient(ipadr, port=502) # activate switch one (manual 4002) rq = client.write_register(4001, 1, unit=1) -print ('%s devicenr %s ipadr %s ' % (time_string,devicenumber,ipadr),file=f) -f.close() -f = open( file_stringpv , 'w') -f.write(str(1)) -f.close() \ No newline at end of file +log.info('on devicenr %d ipadr %s ' % (devicenumber, ipadr)) +with open(file_stringpv, 'w') as f: + f.write(str(1)) diff --git a/modules/smarthome/stiebel/watt.py b/modules/smarthome/stiebel/watt.py index 89041c6a83..abcc8a0f22 100644 --- a/modules/smarthome/stiebel/watt.py +++ b/modules/smarthome/stiebel/watt.py @@ -1,29 +1,21 @@ #!/usr/bin/python3 import sys import os -import time -import json -import getopt -import socket -import struct -import codecs -import binascii -from pymodbus.client.sync import ModbusTcpClient -named_tuple = time.localtime() # getstruct_time -time_string = time.strftime("%m/%d/%Y, %H:%M:%S stiebel watty.py", named_tuple) -devicenumber=str(sys.argv[1]) -ipadr=str(sys.argv[2]) -uberschuss=int(sys.argv[3]) -file_stringpv= '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' +import logging +from smarthome.smartlog import initlog +from smarthome.smartret import writeret +devicenumber = int(sys.argv[1]) +ipadr = str(sys.argv[2]) +uberschuss = int(sys.argv[3]) +initlog("stiebel", devicenumber) +log = logging.getLogger("stiebel") +file_stringpv = '/var/www/html/openWB/ramdisk/smarthome_device_' + str(devicenumber) + '_pv' # pv modus pvmodus = 0 if os.path.isfile(file_stringpv): - f = open( file_stringpv , 'r') - pvmodus =int(f.read()) - f.close() + with open(file_stringpv, 'r') as f: + pvmodus = int(f.read()) aktpower = 0 powerc = 0 -answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) + ',"on":' + str(pvmodus) + '} ' -f1 = open('/var/www/html/openWB/ramdisk/smarthome_device_ret' + str(devicenumber), 'w') -json.dump(answer,f1) -f1.close() \ No newline at end of file +answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) + ',"on":' + str(pvmodus) + '}' +writeret(answer, devicenumber) diff --git a/packages/smarthome/smartlog.py b/packages/smarthome/smartlog.py index 7ced74baf2..f8c1e9fcf2 100644 --- a/packages/smarthome/smartlog.py +++ b/packages/smarthome/smartlog.py @@ -1,7 +1,7 @@ import logging -def initlog(name, devicenumber): +def initlog(name: str, devicenumber: int) -> None: log = logging.getLogger(name) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') log.setLevel(logging.DEBUG) diff --git a/packages/smarthome/smartret.py b/packages/smarthome/smartret.py new file mode 100644 index 0000000000..6f0cc91f4e --- /dev/null +++ b/packages/smarthome/smartret.py @@ -0,0 +1,8 @@ +import json + + +def writeret(jsonstr: str, devicenumber: int) -> None: + fname = '/var/www/html/openWB/ramdisk/smarthome_device_ret' + fname += str(devicenumber) + with open(fname, 'w') as f1: + json.dump(jsonstr, f1) diff --git a/runs/mqttsub.py b/runs/mqttsub.py index 4135eb2f22..7d9de345d5 100755 --- a/runs/mqttsub.py +++ b/runs/mqttsub.py @@ -539,7 +539,7 @@ def on_message(client, userdata, msg): else: print( "invalid payload for topic '" + msg.topic + "': " + msg.payload.decode("utf-8")) if (msg.topic == "openWB/config/set/SmartHome/maxBatteryPower"): - if (0 <= int(msg.payload) <= 10000): + if (0 <= int(msg.payload) <= 30000): f = open('/var/www/html/openWB/ramdisk/smarthomehandlermaxbatterypower', 'w') f.write(msg.payload.decode("utf-8")) f.close() diff --git a/runs/usmarthome/smartnxdacxx.py b/runs/usmarthome/smartnxdacxx.py index 6025a7bce6..d58d3172f5 100644 --- a/runs/usmarthome/smartnxdacxx.py +++ b/runs/usmarthome/smartnxdacxx.py @@ -1,21 +1,22 @@ #!/usr/bin/python3 from usmarthome.smartbase import Sbase from usmarthome.global0 import log +from typing import Dict import subprocess class Snxdacxx(Sbase): - def __init__(self): + def __init__(self) -> None: # setting super().__init__() print('__init__ Snxdacxx executed') - self._smart_paramadd = {} + self._smart_paramadd = {} # type: dict [str,str] self._device_nxdacxxueb = 0 self._device_nxdacxxtype = 0 self.device_nummer = 0 self._dynregel = 1 - def updatepar(self, input_param): + def updatepar(self, input_param: Dict[str, str]) -> None: super().updatepar(input_param) self._smart_paramadd = input_param.copy() self.device_nummer = int(self._smart_paramadd.get('device_nummer', @@ -33,10 +34,10 @@ def updatepar(self, input_param): self._device_nxdacxxtype = valueint else: log.warning("(" + str(self.device_nummer) + ") " + - __class__.__name__ + " überlesen " + key + + "Snxdacxx überlesen " + key + " " + value) - def getwatt(self, uberschuss, uberschussoffset): + def getwatt(self, uberschuss: int, uberschussoffset: int) -> None: self.prewatt(uberschuss, uberschussoffset) forcesend = self.checkbefsend() argumentList = ['python3', self._prefixpy + 'nxdacxx/watt.py', @@ -60,7 +61,7 @@ def getwatt(self, uberschuss, uberschussoffset): str(self._device_ip), str(e1))) self.postwatt() - def turndevicerelais(self, zustand, ueberschussberechnung, updatecnt): + def turndevicerelais(self, zustand: int, ueberschussberechnung: int, updatecnt: int) -> None: self.preturn(zustand, ueberschussberechnung, updatecnt) if (zustand == 1): pname = "/on.py" @@ -68,7 +69,9 @@ def turndevicerelais(self, zustand, ueberschussberechnung, updatecnt): pname = "/off.py" argumentList = ['python3', self._prefixpy + 'nxdacxx' + pname, str(self.device_nummer), str(self._device_ip), - str(self.devuberschuss)] + str(self.devuberschuss), + str(self._device_dacport), + str(self._device_nxdacxxtype)] try: self.proc = subprocess.Popen(argumentList) self.proc.communicate() diff --git a/web/settings/smarthomeconfig.php b/web/settings/smarthomeconfig.php index 5ecea30177..86c53f1f42 100644 --- a/web/settings/smarthomeconfig.php +++ b/web/settings/smarthomeconfig.php @@ -85,7 +85,7 @@ - + @@ -133,8 +133,8 @@ DAC angesteuert über Lan. Der anliegende Überschuss wird in eine Voltzahl zwischen 0.01V und 10.0V umgewandelt. Bezug wird als 0 Volt übertragen. - Wenn die Einschaltbedingung erreicht ist wird alle 30 Sekunden der gerechnete Überschuss übertragen.
- Der konkrete Dac Typ wird im Type definiert
+ Wenn die Einschaltbedingung erreicht ist, wird alle 30 Sekunden der gerechnete Überschuss übertragen.
+ Der konkrete DAC Typ wird im Type definiert.
Mit dem Parameter Updategerät kann eine abweichende Sekundenzahl angegeben werden.
@@ -227,15 +227,16 @@
- Maximaler Überschuss in Watt = v10.0. Es wird kein grösserer Wert übertragen. + Maximal verwertbarer Überschuss des hier gewählten Gerät in Watt = v10.0. Es wird kein grösserer Wert übertragen.
- +
Hier ist das installierte Modell auszuwählen. @@ -243,7 +244,7 @@
- +
@@ -722,7 +723,7 @@ Ja
- Diese Option sorgt dafür, dass das Gerät zusätzlich in den Hausverbrauch eingerechnet wird (Startseite, neues logging). + Bei Nein wird dass das Gerät vom Hausverbrauch abgezogen, bei Ja ist es im Hausverbrauch eingerechnet. (Startseite, neues logging).
@@ -926,7 +927,7 @@
- +
From 5abf25ed12e2cfe015d5e415e133866845662fe8 Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 14 Nov 2022 21:02:01 +0100 Subject: [PATCH 039/201] fefactor module SoC PSA --- modules/soc_psa/psasoc.py | 70 ++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/modules/soc_psa/psasoc.py b/modules/soc_psa/psasoc.py index 0f096f6271..f271993bfc 100755 --- a/modules/soc_psa/psasoc.py +++ b/modules/soc_psa/psasoc.py @@ -9,6 +9,7 @@ import requests import base64 import logging + #these two lines enable debugging at httplib level (requests->urllib3->http.client) # You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. # The only thing missing will be the response.body which is not logged. @@ -16,6 +17,7 @@ import http.client as http_client except ImportError: # Python 2 import httplib as http_client + # to enable http trace set swbedug = 1 swdebug = 0 if swdebug == 1: @@ -28,17 +30,17 @@ requests_log.propagate = True chargepoint=str(sys.argv[1]) -userID=str(sys.argv[2]) +user_id=str(sys.argv[2]) password=str(sys.argv[3]) client_id=str(sys.argv[4]) client_secret=str(sys.argv[5]) manufacturer=str(sys.argv[6]) soccalc=str(sys.argv[7]) -# VehicleId is optional -vehicleId = False +# vehicle_vin is optional +vehicle_vin = False if (len(sys.argv) > 8 ): - vehicleId=str(sys.argv[8]) + vehicle_vin=str(sys.argv[8]) named_tuple = time.localtime() # get struct_time time_string = time.strftime("%m/%d/%Y, %H:%M:%S psasoc lp"+chargepoint, named_tuple) @@ -62,88 +64,97 @@ elif (manufacturer == "Vauxhall"): brand = 'vauxhall.co.uk' realm = 'clientsB2CVauxhall' -vin='?' -data = {'realm': realm,'grant_type':'password','password':password,'username': userID,'scope': scope} + +# oAuth2 login +data = {'realm': realm,'grant_type':'password','password':password,'username': user_id,'scope': scope} headers = {'Content-Type':'application/x-www-form-urlencoded','Authorization': 'Basic %s' % encoded_u} reg = 'https://idpcvs.' + brand + '/am/oauth2/access_token' + f = open('/var/www/html/openWB/ramdisk/psareq1lp'+chargepoint, 'w') f.write(str(reg)) f.write(str(data)) f.write(str(headers)) f.close() + response=requests.post(reg, data=data, headers=headers ) responsetext = response.text responestatus = response.status_code + f = open('/var/www/html/openWB/ramdisk/psareply1lp'+chargepoint, 'w') f.write(responsetext.encode("utf-8")) f.write(str(responestatus)) f.close() + psa_config = json.loads(responsetext) acc_token = psa_config['access_token'] + +# get Vehicles payload = {'client_id':client_id} data = urllib.urlencode(payload) data = data.encode('Big5') reg = 'https://api.groupe-psa.com/connectedcar/v4/user/vehicles?' + data headers = {'Accept':'application/hal+json','Authorization': 'Bearer %s' % acc_token,'x-introspect-realm': realm} + f = open('/var/www/html/openWB/ramdisk/psareq2lp'+chargepoint, 'w') f.write(str(reg)) f.write(str(data)) f.write(str(headers)) f.close() + response=requests.get(reg,headers=headers ) -f = open('/var/www/html/openWB/ramdisk/psareply2lp'+chargepoint, 'w') responsetext = response.text responestatus = response.status_code + +f = open('/var/www/html/openWB/ramdisk/psareply2lp'+chargepoint, 'w') f.write(responsetext.encode("utf-8")) f.write(str(responestatus)) f.close() -vin_list = json.loads(responsetext) -# if VIN is not given, first car will be selected: -if not vehicleId: - vin_id = vin_list ['_embedded']['vehicles'][0]['id'] - vin_vin = vin_list ['_embedded']['vehicles'][0]['vin'] +vehicle_response = json.loads(responsetext) +vehicles = vehicle_response['_embedded']['vehicles'] -else: - # search for entry of given VIN - vin_selected = filter(lambda x: x['vin'] == vehicleId, vin_list['_embedded']['vehicles']) - vin_id = vin_selected [0]['id'] - vin_vin = vin_selected [0]['vin'] +# Filter List for given VIN or select first entry +vehicle_selected = next(vehicle for vehicle in vehicles if vehicle['vin'] == vehicle_vin) if vehicle_vin else vehicles[0] +vehicle_id = vehicle_selected['id'] f = open('/var/www/html/openWB/ramdisk/psareply2filterVINlp'+chargepoint, 'w') -f.write('VIN: ' + str(vin_vin)) -f.write(', ID: ' + str(vin_id)) +f.write('VIN: ' + str(vehicle_selected['vin'])) +f.write(', ID: ' + str(vehicle_id)) f.close() +# get Vehicles Status payload = {'client_id':client_id} data = urllib.urlencode(payload) data = data.encode('Big5') '/user/vehicles/{id}/status' -reg = 'https://api.groupe-psa.com/connectedcar/v4/user/vehicles/' + vin_id + '/status?' + data +reg = 'https://api.groupe-psa.com/connectedcar/v4/user/vehicles/' + vehicle_id + '/status?' + data headers = {'Accept':'application/hal+json','Authorization': 'Bearer %s' % acc_token,'x-introspect-realm': realm} + f = open('/var/www/html/openWB/ramdisk/psareq3lp'+chargepoint, 'w') f.write(str(reg)) f.write(str(data)) f.write(str(headers)) f.close() + response=requests.get(reg,headers=headers ) -f = open('/var/www/html/openWB/ramdisk/psareply3lp'+chargepoint, 'w') responsetext = response.text responestatus = response.status_code + +f = open('/var/www/html/openWB/ramdisk/psareply3lp'+chargepoint, 'w') f.write(responsetext.encode("utf-8")) f.write(str(responestatus)) f.close() -batt = json.loads(responsetext) -# filter to only include type=Electric but remove all others. Seen type=Fuel and type=Electric being returned. -batt = filter(lambda x: x['type'] == 'Electric', batt['energy']) -soc = batt[0]['level'] +status_response = json.loads(responsetext) +energies = status_response['energy'] -#soc = batt['energy'][0]['level'] -#print(time_string,'soc lp'+chargepoint,soc) +# Filter to only include type=Electric but remove all others. Seen type=Fuel and type=Electric being returned. +energy_selected = next(energy for energy in energies if energy['type'] == 'Electric') +soc = energy_selected['level'] +fetchedsoctime = energy_selected['updatedAt'] if (int(soccalc) == 0): - #manual calculation not enabled, using existing logic + # manual calculation not enabled, using existing logic if (int(chargepoint) == 1): f = open('/var/www/html/openWB/ramdisk/soc', 'w') if (int(chargepoint) == 2): @@ -151,7 +162,7 @@ f.write(str(soc)) f.close() else: - #manual calculation enabled, using new logic with timestamp + # manual calculation enabled, using new logic with timestamp if (int(chargepoint) == 1): f = open('/var/www/html/openWB/ramdisk/psasoc', 'w') if (int(chargepoint) == 2): @@ -159,7 +170,6 @@ f.write(str(soc)) f.close() # getting timestamp of fetched SoC - fetchedsoctime = batt[0]['updatedAt'] soct = time.strptime(fetchedsoctime, "%Y-%m-%dT%H:%M:%SZ") soctime = time.mktime(soct) # adding one hour to UTC to get CET From a3e8124c8f8cbfe327a217a7e53e1d80e9c72309 Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 14 Nov 2022 21:03:59 +0100 Subject: [PATCH 040/201] solve wrong indentation type --- runs/updateConfig.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runs/updateConfig.sh b/runs/updateConfig.sh index a7218e2037..89dd5fe6ab 100755 --- a/runs/updateConfig.sh +++ b/runs/updateConfig.sh @@ -1834,7 +1834,7 @@ updateConfig(){ echo "slaveModeSlowRamping=1" >> $ConfigFile fi if ! grep -Fq "slaveModeMinimumAdjustmentInterval=" $ConfigFile; then - echo "slaveModeMinimumAdjustmentInterval=15" >> $ConfigFile + echo "slaveModeMinimumAdjustmentInterval=15" >> $ConfigFile fi if ! grep -Fq "standardSocketInstalled=" /var/www/html/openWB/openwb.conf then From 03d9bfbc3b826e59aa397fd669d2ed9cabdbfeba Mon Sep 17 00:00:00 2001 From: DetMoerk <76627235+DetMoerk@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:38:27 +0100 Subject: [PATCH 041/201] set interval on debug to 2 Minutes API documentation says only one request all 2 minutes. As we had Returncode 400 Errors on systems using Debug Level 1 / 2 this is the Change to be in the specification --- modules/soc_eq/main.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/soc_eq/main.sh b/modules/soc_eq/main.sh index dc46b656e9..a3936f82b1 100755 --- a/modules/soc_eq/main.sh +++ b/modules/soc_eq/main.sh @@ -77,7 +77,7 @@ else tmpintervall=$(( 60 * 6 )) fi if (( socDebug > 0 )); then - tmpintervall=6 + tmpintervall=12 fi if (( soctimer < tmpintervall )); then From 5e9b831b54a76e80e1eac97df46350a21c9083fe Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Tue, 15 Nov 2022 14:26:02 +0100 Subject: [PATCH 042/201] fix monthly graph --- web/themes/colors/powergraph.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 445bcda1e1..87a43e1a17 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -262,7 +262,7 @@ class PowerGraph { unsubscribeMonthGraph(); this.initCounter = 0; this.staging.map((segment, i) => { - if (i == 3) { + if ((i == 3) || (i == 6)) { segment.map((line, i) => { if (line.length > 1) { this.rawData.push(line) } }) @@ -289,6 +289,7 @@ class PowerGraph { } } } + updateEnergyValues() { if (this.rawData.length) { const startValues = this.rawData[0].split(','); @@ -317,10 +318,17 @@ class PowerGraph { let pvCharged = this.graphData.reduce((prev, cur) => { return prev + (cur.chargingPv / 12); }, 0) - wbdata.historicSummary.chargingPv.energy = pvCharged / 1000; - wbdata.usageSummary.chargingPv.energy = pvCharged / 1000; + wbdata.historicSummary.charging.energyPv = pvCharged / 1000; + wbdata.usageSummary.charging.energyPv = pvCharged / 1000; + let batCharged = this.graphData.reduce((prev, cur) => { + return prev + (cur.chargingBat / 12); + }, 0) + wbdata.historicSummary.charging.energyBat = batCharged / 1000; + wbdata.usageSummary.charging.energyBat = batCharged / 1000; + } } + extractLiveValues(payload) { const elements = payload.split(","); const now = new Date(Date.now()); @@ -440,6 +448,8 @@ class PowerGraph { if (values.selfUsage < 0) { values.selfUsage = 0; }; if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { values.chargingPv = values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) + values.chargingBat = values.charging * values.batOut / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) + } else { values.chargingPv = 0; } From 7723b27cdc36bbf490ec70723d0dea49c3a13fe6 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Tue, 15 Nov 2022 17:55:55 +0100 Subject: [PATCH 043/201] show battery portion for charging --- web/themes/colors/powerdata.js | 22 +++++------ web/themes/colors/powergraph.js | 1 + web/themes/colors/yieldmeter.js | 65 +++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/web/themes/colors/powerdata.js b/web/themes/colors/powerdata.js index ebab76fe52..032c2b1aaa 100644 --- a/web/themes/colors/powerdata.js +++ b/web/themes/colors/powerdata.js @@ -42,12 +42,12 @@ class WbData { }; this.usageSummary = { - "evuOut": { name: "Export", power: 0, energy: 0, color: "white" }, - "charging": { name: "Laden", power: 0, energy: 0, color: "white" }, - "devices": { name: "Geräte", power: 0, energy: 0, color: "white" }, - "batIn": { name: "> Bat", power: 0, energy: 0, color: "white" }, - "house": { name: "Haus", power: 0, energy: 0, color: "white" }, - "chargingPv": { name: "PVCharge", power: 0, energy: 0, color: "white"} + "evuOut": { name: "Export", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "charging": { name: "Laden", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "devices": { name: "Geräte", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "batIn": { name: "> Bat", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "house": { name: "Haus", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "chargingPv": { name: "PVCharge", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white"} }; this.historicSummary = { @@ -55,11 +55,11 @@ class WbData { "pv": { name: "PV", power: 0, energy: 0, color: "white" }, "batOut": { name: "Bat >", power: 0, energy: 0, color: "white" }, "evuOut": { name: "Export", power: 0, energy: 0, color: "white" }, - "charging": { name: "Laden", power: 0, energy: 0, color: "white" }, - "batIn": { name: "> Bat", power: 0, energy: 0, color: "white" }, - "house": { name: "Haus", power: 0, energy: 0, color: "white" }, - "devices": { name: "Geräte", power: 0, energy: 0, color: "white" }, - "chargingPv": {name: "PVCharge", power: 0, energy: 0, color: "white"} + "charging": { name: "Laden", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "batIn": { name: "> Bat", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "house": { name: "Haus", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "devices": { name: "Geräte", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white" }, + "chargingPv": {name: "PVCharge", power: 0, energy: 0, energyPv: 0, energyBat: 0, color: "white"} }; this.usageDetails = [this.usageSummary.evuOut]; diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 87a43e1a17..06702067ff 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -452,6 +452,7 @@ class PowerGraph { } else { values.chargingPv = 0; + values.chargingBat = 0; } return values; } diff --git a/web/themes/colors/yieldmeter.js b/web/themes/colors/yieldmeter.js index 711aea1bf7..993b58345d 100644 --- a/web/themes/colors/yieldmeter.js +++ b/web/themes/colors/yieldmeter.js @@ -35,6 +35,7 @@ class YieldMeter { this.chargeColor = 'var(--color-charging)'; this.axisColor = 'var(--color-axis)'; this.gridColor = 'var(--color-grid)'; + this.batColor = 'var(--color-battery)'; d3.select("button#energyLeftButton") .on("click", shiftLeft) d3.select("button#energyRightButton") @@ -88,9 +89,7 @@ class YieldMeter { } drawChart(svg) { - let pvcdata = this.plotdata.filter(d => (d.name == "PVCharge")) let chargedata = this.plotdata.filter(d => d.name == "Laden") - this.plotdata = this.plotdata.filter(d => (d.name != "PVCharge")) const ymax = d3.max(this.plotdata, (d) => d.energy); this.xScale.domain(this.plotdata.map((d) => d.name)); this.yScale.domain([0, Math.ceil(ymax)]); @@ -111,22 +110,39 @@ class YieldMeter { .attr("fill", (d) => d.color); // Display the PV Charging inner bar - if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { + if ((chargedata.length > 0) && (chargedata[0].energyPv > 0)) { const pvcBargroup = svg .selectAll(".pvcBar") - .data(pvcdata) + .data(chargedata) .enter() .append("g") pvcBargroup .append("rect") .attr("class", "bar") .attr("x", (d) => this.xScale("Laden") + this.xScale.bandwidth() / 6) - .attr("y", (d) => this.yScale(d.energy)) + .attr("y", (d) => this.yScale(d.energyPv)) .attr("width", this.xScale.bandwidth() * 2 / 3) - .attr("height", (d) => (this.height - this.yScale(d.energy) - this.margin.top - this.margin.bottom)) + .attr("height", (d) => (this.height - this.yScale(d.energyPv) - this.margin.top - this.margin.bottom)) .attr("fill", this.pvColor) .attr("fill-opacity", "66%"); } + // Display the Bat Charging inner bar + if ((chargedata.length > 0) && (chargedata[0].energyBat > 0)) { + const pvcBargroup = svg + .selectAll(".batcBar") + .data(chargedata) + .enter() + .append("g") + pvcBargroup + .append("rect") + .attr("class", "bar") + .attr("x", (d) => this.xScale("Laden") + this.xScale.bandwidth() / 6) + .attr("y", (d) => this.yScale(d.energyBat+d.energyPv)) + .attr("width", this.xScale.bandwidth() * 2 / 3) + .attr("height", (d) => (this.height - this.yScale(d.energyBat) - this.margin.top - this.margin.bottom)) + .attr("fill", this.batColor) + .attr("fill-opacity", "66%"); + } const yAxisGenerator = d3.axisLeft(this.yScale) .tickFormat(function (d) { return ((d > 0) ? d : ""); @@ -173,7 +189,7 @@ class YieldMeter { .append("text") .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) .attr("y", (d) => { - if (d.name == "Laden" && pvcdata.length > 0) { + if (d.energyPv > 0) { return this.yScale(d.energy) - 25 } else { return this.yScale(d.energy) - 10 @@ -185,22 +201,18 @@ class YieldMeter { .text((d) => (formatWattH(d.energy * 1000))); // add a PV percentage tag to the charging bar - if ((pvcdata.length > 0) && (chargedata.length > 0) && (chargedata[0].energy > 0)) { - let pvRatio = Math.round(pvcdata[0].energy / chargedata[0].energy * 100) - - const pvtags = svg.selectAll(".pvtag") - .data(chargedata) - .enter() - .append("g"); - pvtags - .append("text") - .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) - .attr("y", (d) => this.yScale(d.energy) - 10) - .attr("font-size", this.labelfontsize - 2) - .attr("text-anchor", "middle") - .attr("fill", (d) => this.pvColor) - .text((d) => ("(PV: " + pvRatio.toLocaleString(undefined) + " %)")); - } + const pvtags = svg.selectAll(".pvtag") + .data(this.plotdata) + .enter() + .append("g"); + pvtags + .append("text") + .attr("x", (d) => this.xScale(d.name) + this.xScale.bandwidth() / 2) + .attr("y", (d) => this.yScale(d.energy) - 10) + .attr("font-size", this.labelfontsize - 2) + .attr("text-anchor", "middle") + .attr("fill", (d) => this.pvColor) + .text((d) => this.pvString(d)); // Add category labels labels @@ -214,6 +226,13 @@ class YieldMeter { } + pvString( item ) { + if (item.energyPv > 0 || item.energyBat > 0) { + return ("(PV: " + (Math.round((item.energyPv + item.energyBat)/ item.energy *100)).toLocaleString(undefined) + " %)"); + } else { + return ""; + } + } updateHeading() { var heading = "Energie "; From edf8c3a1fe0dfd6ec27f2cd6886ffab348401a28 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Tue, 15 Nov 2022 18:02:43 +0100 Subject: [PATCH 044/201] battery contribution for charging for monthly graph --- web/themes/colors/powergraph.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 06702067ff..97bf380be7 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -472,6 +472,7 @@ class PowerGraph { // charge points values.charging = this.calcMonthlyValue(7, elements, oldElements); values.chargingPv = this.calcMonthlyValue(29, elements, oldElements); + values.chargingBat = this.calcMonthlyValue(30, elements, oldElements); var i; for (i = 0; i < 3; i++) { values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); @@ -515,6 +516,9 @@ class PowerGraph { this.graphRefreshCounter = 0; wbdata.historicSummary.chargingPv.energy = 0; wbdata.usageSummary.chargingPv.energy = 0; + wbdata.historicSummary.chargingBat.energy = 0; + wbdata.usageSummary.chargingBat.energy = 0; + } resetDayGraph() { From 6d03caca44366b6acadf6cad943db0b68405bb3e Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Tue, 15 Nov 2022 22:34:24 +0100 Subject: [PATCH 045/201] fix calculations --- web/themes/colors/powergraph.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 97bf380be7..0c0c09fe1e 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -274,7 +274,7 @@ class PowerGraph { const values = this.extractMonthValues(line, a[i - 1]); if ((values.date.getFullYear() == wbdata.graphMonth.year) && ((values.date.getMonth() == wbdata.graphMonth.month) - || ((values.date.getMonth() == (wbdata.graphMonth.month + 1)) && (values.date.getDate == 1)) + || ((values.date.getMonth() == (wbdata.graphMonth.month + 1)) && (values.date.getDate() == 1)) ) ) { this.graphData.push(values); } } @@ -447,8 +447,8 @@ class PowerGraph { values.selfUsage = values.solarPower - values.gridPush; if (values.selfUsage < 0) { values.selfUsage = 0; }; if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { - values.chargingPv = values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) - values.chargingBat = values.charging * values.batOut / (values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) + values.chargingPv = Math.floor ((values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut))) + values.chargingBat = Math.floor ((values.charging * values.batOut / (values.solarPower + values.gridPull + values.batOut))) } else { values.chargingPv = 0; @@ -509,6 +509,7 @@ class PowerGraph { resetLiveGraph() { // fresh reload of the graph + console.log ("resetlivegraph") this.initialized = false; this.initCounter = 0; this.initialGraphData = []; From c1afa85ae662b14c66fd8e71455ae821ed4e2778 Mon Sep 17 00:00:00 2001 From: Yannik Hampe Date: Wed, 12 Oct 2022 17:13:48 +0200 Subject: [PATCH 046/201] use relative paths in zielladen.sh&loadconfig.sh --- loadconfig.sh | 4 +++- zielladen.sh | 36 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/loadconfig.sh b/loadconfig.sh index b08e2bddbc..315b73f399 100755 --- a/loadconfig.sh +++ b/loadconfig.sh @@ -1,3 +1,5 @@ +OPENWBBASEDIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) + while read -r x; do value="${x#*=}" if [[ "${value:0:1}" == "'" ]] ; then @@ -7,4 +9,4 @@ while read -r x; do else export "$x" fi -done < /var/www/html/openWB/openwb.conf +done < "$OPENWBBASEDIR/openwb.conf" diff --git a/zielladen.sh b/zielladen.sh index 216647bb09..e95eb0acdd 100755 --- a/zielladen.sh +++ b/zielladen.sh @@ -1,18 +1,20 @@ #!/bin/bash +OPENWBBASEDIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) +RAMDISKDIR="${OPENWBBASEDIR}/ramdisk" ziellademodus(){ #verbleibende Zeit berechnen dateaktuell=$(date '+%Y-%m-%d %H:%M') epochdateaktuell=$(date -d "$dateaktuell" +"%s") - zielladenkorrektura=$( ramdisk/ladungdurchziel - echo 0 > ramdisk/zielladenkorrektura - sed -e "s/zielladenaktivlp1=.*/zielladenaktivlp1=0/" openwb.conf > ramdisk/openwb.conf && mv ramdisk/openwb.conf openwb.conf && chmod 777 openwb.conf - runs/set-current.sh 0 m + echo 0 > "$RAMDISKDIR/ladungdurchziel" + echo 0 > "$RAMDISKDIR/zielladenkorrektura" + sed -i 's/^\(zielladenaktivlp1=\).*/\10/' "$OPENWBBASEDIR/openwb.conf" + "$OPENWBBASEDIR/runs/set-current.sh" 0 m fi else if (( zuladendewh > moeglichewh )); then if (( ladestatus == 0 )); then - runs/set-current.sh "$zielladenalp1" m + "$OPENWBBASEDIR/runs/set-current.sh" "$zielladenalp1" m openwbDebugLog "MAIN" 1 "setzte Soctimer hoch zum Abfragen des aktuellen SoC" - echo 20000 > /var/www/html/openWB/ramdisk/soctimer - echo 1 > ramdisk/ladungdurchziel + echo 20000 > "$RAMDISKDIR/soctimer" + echo 1 > "$RAMDISKDIR/ladungdurchziel" exit 0 else if (( diffwh > 1000 )); then - if test $(find /var/www/html/openWB/ramdisk/zielladenkorrektura -mmin +10); then + if test $(find "$RAMDISKDIR/zielladenkorrektura" -mmin +10); then zielladenkorrektura=$(( zielladenkorrektura + 1 )) - echo $zielladenkorrektura > ramdisk/zielladenkorrektura + echo $zielladenkorrektura > "$RAMDISKDIR/zielladenkorrektura" zielneu=$(( zielladenalp1 + zielladenkorrektura )) if (( zielneu > zielladenmaxalp1)); then zielneu=$zielladenmaxalp1 fi - runs/set-current.sh "$zielneu" m + "$OPENWBBASEDIR/runs/set-current.sh" "$zielneu" m exit 0 fi fi @@ -64,14 +66,14 @@ ziellademodus(){ else if (( ladestatus == 1 )); then if (( diffwh < -1000 )); then - if test $(find /var/www/html/openWB/ramdisk/zielladenkorrektura -mmin +10); then + if test $(find "$RAMDISKDIR/zielladenkorrektura" -mmin +10); then zielladenkorrektura=$(( zielladenkorrektura - 1 )) - echo $zielladenkorrektura > ramdisk/zielladenkorrektura + echo $zielladenkorrektura > "$RAMDISKDIR/zielladenkorrektura" zielneu=$(( zielladenalp1 + zielladenkorrektura )) if (( zielneu < minimalstromstaerke )); then zielneu=$minimalstromstaerke fi - runs/set-current.sh "$zielneu" m + "$OPENWBBASEDIR/runs/set-current.sh" "$zielneu" m exit 0 fi fi From 819ac270b40f9d342e461e1384d74234689e7140 Mon Sep 17 00:00:00 2001 From: Yannik Hampe Date: Wed, 12 Oct 2022 19:08:42 +0200 Subject: [PATCH 047/201] take charging efficiency into account for "zielladen" --- web/settings/settings.php | 15 +++++++++++++++ zielladen.sh | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/web/settings/settings.php b/web/settings/settings.php index 538b6ec657..98dfbd5b47 100644 --- a/web/settings/settings.php +++ b/web/settings/settings.php @@ -702,6 +702,21 @@ function visibility_electricitytariff() { Ampere mit denen geladen werden soll um den Ziel SoC zu erreichen. + +
+ +
+ + + Wert in Prozent, der den gemittelten Wirkungsgrad der Ladeelektronik angibt.
+ Durch Verluste in der Ladeelektronik (z. B. Umwandlung Wechselspannung in Gleichspannung) gelangt nicht die komplette Energie, welche durch den Zähler in der Wallbox gemesen wird, im Akku des Fahrzeugs. + Der anzugebende Wert liegt bei gängigen Fahrzeugen im Bereich 90-95%. Eine Ausnahme stellt der Zoe dar, dessen Chameleonlader je nach Modellversion und freigegebener Leistung der Wallbox teilweise nur auf ca. 50% kommt.
+ Liegen die Angaben der Wallbox und des Fahrzeugs nach der Ladung mehrere Prozent auseinander, dann kann mit dieser Einstellung eine Feinabstimmung erfolgen:
+ SoC an der Wallbox zu hoch: Wirkungsgrad um ein paar Prozent reduzieren
+ SoC an der Wallbox zu gering: Wirkungsgrad um ein paar Prozent erhöhen +
+
+
diff --git a/zielladen.sh b/zielladen.sh index e95eb0acdd..5b2a7d609a 100755 --- a/zielladen.sh +++ b/zielladen.sh @@ -12,12 +12,13 @@ ziellademodus(){ epochdateziel=$(date -d "$zielladenuhrzeitlp1" +"%s") zeitdiff=$(( epochdateziel - epochdateaktuell )) minzeitdiff=$(( zeitdiff / 60 )) + wirkungsgrad=${wirkungsgradlp1:-100} # zu ladende Menge ermitteln soc=$(<"$RAMDISKDIR/soc") zuladendersoc=$(( zielladensoclp1 - soc )) akkuglp1wh=$(( akkuglp1 * 1000 )) - zuladendewh=$(( akkuglp1wh * zuladendersoc / 100)) + zuladendewh=$(( akkuglp1wh * zuladendersoc / wirkungsgrad )) #ladeleistung ermitteln lademaxwh=$(( zielladenmaxalp1 * zielladenphasenlp1 * 230 )) From 9d18ef8ae267fb361bae9742618ff5a4b66e261f Mon Sep 17 00:00:00 2001 From: Yannik Hampe Date: Wed, 2 Nov 2022 10:24:19 +0100 Subject: [PATCH 048/201] fix indention --- web/settings/settings.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/settings/settings.php b/web/settings/settings.php index 98dfbd5b47..28814003f8 100644 --- a/web/settings/settings.php +++ b/web/settings/settings.php @@ -262,7 +262,7 @@ function visibility_isss() { } addressStr = addressStr + ', ' + this.address.postalCode + ' ' + this.address.city; $('#tibberHomesDropdown').append(''); - }); + }); $('#tibberhomeIdModal').find('.modal-header').removeClass('bg-danger'); $('#tibberhomeIdModal').find('.modal-header').addClass('bg-success'); $('#tibberhomeIdModalOkBtn').show(); @@ -287,7 +287,7 @@ function visibility_isss() { $('#tibberModalSelectHomeIdDiv').hide(); $('#tibberhomeid').val(''); $('#tibberhomeIdModal').modal("show"); - }) + }) }); $('#verifyTibberBtn').click(function(){ @@ -344,10 +344,10 @@ function visibility_isss() {
- - -
+ + +
From 20c60640905b773754d615cf4ef2eb9fdff50611 Mon Sep 17 00:00:00 2001 From: "cshagen@hagens.ch" Date: Wed, 16 Nov 2022 09:56:20 +0100 Subject: [PATCH 049/201] fix monthly graph --- web/themes/colors/powergraph.js | 98 ++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/web/themes/colors/powergraph.js b/web/themes/colors/powergraph.js index 0c0c09fe1e..446a1863ee 100644 --- a/web/themes/colors/powergraph.js +++ b/web/themes/colors/powergraph.js @@ -247,43 +247,43 @@ class PowerGraph { updateMonth(topic, payload) { if (payload != 'empty') { + const serialNo = topic.substring(29, topic.length); + var segment = payload.toString().split("\n"); if (segment[0] == "") { segment = []; } - const serialNo = topic.substring(29, topic.length); if (serialNo != "") { if (typeof (this.staging[+serialNo - 1]) === 'undefined') { this.staging[+serialNo - 1] = segment; this.initCounter++; } } + if (this.initCounter == 12) {// Initialization complete unsubscribeMonthGraph(); this.initCounter = 0; this.staging.map((segment, i) => { - if ((i == 3) || (i == 6)) { + if ((i == 4) || (i == 5)) { segment.map((line, i) => { if (line.length > 1) { this.rawData.push(line) } }) } + if (i == 2) { + this.monthlyAmounts = segment[0].split(','); + } }) this.rawData.map((line, i, a) => { - if (i > 0) { - if (line != "0" && line != "") { - const values = this.extractMonthValues(line, a[i - 1]); - if ((values.date.getFullYear() == wbdata.graphMonth.year) - && ((values.date.getMonth() == wbdata.graphMonth.month) - || ((values.date.getMonth() == (wbdata.graphMonth.month + 1)) && (values.date.getDate() == 1)) - ) - ) { this.graphData.push(values); } + if (line != "0" && line != "") { + const values = this.extractMonthValues(line, a[i - 1]); + if (values.date.getFullYear() == wbdata.graphMonth.year && values.date.getMonth() == wbdata.graphMonth.month) { + this.graphData.push(values); } - } else { - // const values = this.extractValues(line, []); } }); + this.updateGraph(); - this.updateEnergyValues(); + this.updateMonthlyEnergyValues(); wbdata.monthGraphUpdated(); // setTimeout(() => this.activateDay(), 300000) } @@ -325,10 +325,32 @@ class PowerGraph { }, 0) wbdata.historicSummary.charging.energyBat = batCharged / 1000; wbdata.usageSummary.charging.energyBat = batCharged / 1000; - } } - + updateMonthlyEnergyValues() { + if (this.rawData.length) { + wbdata.historicSummary.pv.energy = +this.monthlyAmounts[3]; + wbdata.historicSummary.evuIn.energy = +this.monthlyAmounts[1]; + wbdata.historicSummary.batOut.energy = +this.monthlyAmounts[9]; + wbdata.historicSummary.evuOut.energy = +this.monthlyAmounts[2];; + wbdata.historicSummary.charging.energy = +this.monthlyAmounts[7]; + var deviceEnergySum = 0; + var deviceEnergy = 0; + let deviceIndex = 32; + for (var i = 0; i < 9; i++) { + deviceEnergy = +this.monthlyAmounts[deviceIndex + 3 * i]; + if (deviceEnergy < 0) { deviceEnergy = 0 } + deviceEnergySum = deviceEnergySum + deviceEnergy + wbdata.historicSummary['sh' + i].energy = deviceEnergy + } + wbdata.historicSummary.devices.energy = deviceEnergySum; + wbdata.historicSummary.batIn.energy = +this.monthlyAmounts[12]; + wbdata.historicSummary.house.energy = wbdata.historicSummary.evuIn.energy + wbdata.historicSummary.pv.energy + wbdata.historicSummary.batOut.energy + - wbdata.historicSummary.evuOut.energy - wbdata.historicSummary.batIn.energy - wbdata.historicSummary.charging.energy - wbdata.historicSummary.devices.energy; + wbdata.historicSummary.charging.energyPv = +this.monthlyAmounts[29]; + wbdata.historicSummary.charging.energyBat = +this.monthlyAmounts[30]; + } + } extractLiveValues(payload) { const elements = payload.split(","); const now = new Date(Date.now()); @@ -447,9 +469,9 @@ class PowerGraph { values.selfUsage = values.solarPower - values.gridPush; if (values.selfUsage < 0) { values.selfUsage = 0; }; if ((values.solarPower + values.gridPull + values.batOut - values.gridPush - values.batIn) > 0) { - values.chargingPv = Math.floor ((values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut))) - values.chargingBat = Math.floor ((values.charging * values.batOut / (values.solarPower + values.gridPull + values.batOut))) - + values.chargingPv = Math.floor((values.charging * values.solarPower / (values.solarPower + values.gridPull + values.batOut))) + values.chargingBat = Math.floor((values.charging * values.batOut / (values.solarPower + values.gridPull + values.batOut))) + } else { values.chargingPv = 0; values.chargingBat = 0; @@ -457,41 +479,40 @@ class PowerGraph { return values; } - extractMonthValues(payload, oldPayload) { + extractMonthValues(payload) { if (payload != "0") { const elements = payload.split(","); - const oldElements = oldPayload.split(","); var values = {}; - values.date = new Date(d3.timeParse("%Y%m%d%H%M")(oldElements[0] + '1200')); + values.date = new Date(d3.timeParse("%Y%m%d%H%M")(elements[0] + '1200')); // evu - values.gridPull = this.calcMonthlyValue(1, elements, oldElements); - values.gridPush = this.calcMonthlyValue(2, elements, oldElements); + values.gridPull = this.calcMonthlyValue(1, elements); + values.gridPush = this.calcMonthlyValue(2, elements); // pv - values.solarPower = this.calcMonthlyValue(3, elements, oldElements); + values.solarPower = this.calcMonthlyValue(3, elements); values.inverter = 0; // charge points - values.charging = this.calcMonthlyValue(7, elements, oldElements); - values.chargingPv = this.calcMonthlyValue(29, elements, oldElements); - values.chargingBat = this.calcMonthlyValue(30, elements, oldElements); + values.charging = this.calcMonthlyValue(7, elements); + values.chargingPv = this.calcMonthlyValue(29, elements); + values.chargingBat = this.calcMonthlyValue(30, elements); var i; for (i = 0; i < 3; i++) { - values["lp" + i] = this.calcMonthlyValue(4 + i, elements, oldElements); + values["lp" + i] = this.calcMonthlyValue(4 + i, elements); } for (i = 3; i < 8; i++) { - values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements, oldElements); + values["lp" + i] = this.calcMonthlyValue(12 + i - 3, elements); } values.soc1 = +elements[21]; values.soc2 = +elements[22]; // smart home for (i = 0; i < 10; i++) { - values["sh" + i] = this.calcMonthlyValue(19 + i, elements, oldElements); + values["sh" + i] = this.calcMonthlyValue(19 + i, elements); } //consumers - values.co0 = this.calcMonthlyValue(10, elements, oldElements); - values.co1 = this.calcMonthlyValue(12, elements, oldElements); + values.co0 = this.calcMonthlyValue(10, elements); + values.co1 = this.calcMonthlyValue(12, elements); //battery - values.batIn = this.calcMonthlyValue(17, elements, oldElements); - values.batOut = this.calcMonthlyValue(18, elements, oldElements); + values.batIn = this.calcMonthlyValue(17, elements); + values.batOut = this.calcMonthlyValue(18, elements); values.batterySoc = +elements[20]; // calculated values values.housePower = values.gridPull + values.solarPower + values.batOut @@ -509,7 +530,7 @@ class PowerGraph { resetLiveGraph() { // fresh reload of the graph - console.log ("resetlivegraph") + console.log("resetlivegraph") this.initialized = false; this.initCounter = 0; this.initialGraphData = []; @@ -519,7 +540,7 @@ class PowerGraph { wbdata.usageSummary.chargingPv.energy = 0; wbdata.historicSummary.chargingBat.energy = 0; wbdata.usageSummary.chargingBat.energy = 0; - + } resetDayGraph() { @@ -545,9 +566,8 @@ class PowerGraph { } return val; } - calcMonthlyValue(i, array, oldArray) { - var val = (array[i] - oldArray[i]); - + calcMonthlyValue(i, array) { + var val = Math.floor(+array[i] * 1000) if (val < 0 || val > 150000) { val = 0; } From 610a6b312ab8c42ee91d2b947859a699515fa768 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 16 Nov 2022 14:03:35 +0100 Subject: [PATCH 050/201] changes from core: split modules --- .../{alpha_ess => chargepoints}/__init__.py | 0 .../internal_openwb}/__init__.py | 0 .../internal_openwb/chargepoint_module.py | 0 .../internal_openwb/socket.py | 0 packages/modules/common/simcount/__init__.py | 2 +- packages/modules/common/simcount/_simcount.py | 2 +- .../modules/common/simcount/_simcounter.py | 2 +- .../common/simcount/_simcounter_store.py | 2 +- ...imcounter_state.py => simcounter_state.py} | 0 packages/modules/conftest.py | 3 + packages/modules/{byd => devices}/__init__.py | 0 .../alpha_ess}/__init__.py | 0 .../modules/{ => devices}/alpha_ess/bat.py | 10 +-- .../modules/{ => devices}/alpha_ess/config.py | 0 .../{ => devices}/alpha_ess/counter.py | 6 +- .../modules/{ => devices}/alpha_ess/device.py | 8 +-- .../{ => devices}/alpha_ess/inverter.py | 10 +-- .../batterx}/__init__.py | 0 packages/modules/{ => devices}/batterx/bat.py | 10 +-- .../{ => devices}/batterx/batterx_test.py | 51 +++++++------- .../modules/{ => devices}/batterx/config.py | 0 .../modules/{ => devices}/batterx/counter.py | 10 +-- .../modules/{ => devices}/batterx/device.py | 10 +-- .../modules/{ => devices}/batterx/inverter.py | 10 +-- .../{enphase => devices/byd}/__init__.py | 0 packages/modules/{ => devices}/byd/bat.py | 12 ++-- .../modules/{ => devices}/byd/byd_test.py | 2 +- .../byd/byd_test_sample_home.html | 45 ++++++------ .../byd/byd_test_sample_rundata.html | 2 +- packages/modules/{ => devices}/byd/config.py | 4 +- packages/modules/{ => devices}/byd/device.py | 6 +- .../carlo_gavazzi}/__init__.py | 0 .../{ => devices}/carlo_gavazzi/config.py | 0 .../{ => devices}/carlo_gavazzi/counter.py | 10 +-- .../{ => devices}/carlo_gavazzi/device.py | 4 +- .../discovergy}/__init__.py | 0 .../modules/{ => devices}/discovergy/api.py | 0 .../{ => devices}/discovergy/api_test.py | 2 +- .../{ => devices}/discovergy/config.py | 0 .../{ => devices}/discovergy/counter.py | 4 +- .../{ => devices}/discovergy/device.py | 4 +- .../{ => devices}/discovergy/device_test.py | 4 +- .../{ => devices}/discovergy/inverter.py | 4 +- .../modules/{ => devices}/discovergy/utils.py | 9 +-- packages/modules/{ => devices}/e3dc/bat.py | 2 +- packages/modules/{ => devices}/e3dc/config.py | 0 .../modules/{ => devices}/e3dc/counter.py | 2 +- packages/modules/{ => devices}/e3dc/device.py | 14 ++-- .../modules/{ => devices}/e3dc/inverter.py | 2 +- .../{good_we => devices/enphase}/__init__.py | 0 .../modules/{ => devices}/enphase/config.py | 4 +- .../modules/{ => devices}/enphase/counter.py | 2 +- .../modules/{ => devices}/enphase/device.py | 18 ++--- .../modules/{ => devices}/enphase/inverter.py | 2 +- .../{http => devices/fronius}/__init__.py | 0 packages/modules/{ => devices}/fronius/bat.py | 12 ++-- .../modules/{ => devices}/fronius/bat_test.py | 4 +- .../modules/{ => devices}/fronius/config.py | 0 .../{ => devices}/fronius/counter_s0.py | 10 +-- .../{ => devices}/fronius/counter_s0_test.py | 4 +- .../{ => devices}/fronius/counter_sm.py | 14 ++-- .../{ => devices}/fronius/counter_sm_test.py | 4 +- .../modules/{ => devices}/fronius/device.py | 12 ++-- .../modules/{ => devices}/fronius/inverter.py | 10 +-- .../{ => devices}/fronius/inverter_test.py | 4 +- .../{ => devices}/fronius/meter_test.py | 2 +- .../{huawei => devices/good_we}/__init__.py | 0 packages/modules/{ => devices}/good_we/bat.py | 6 +- .../modules/{ => devices}/good_we/config.py | 4 +- .../modules/{ => devices}/good_we/counter.py | 6 +- .../modules/{ => devices}/good_we/device.py | 9 +-- .../modules/{ => devices}/good_we/inverter.py | 6 +- .../http}/__init__.py | 0 packages/modules/{ => devices}/http/api.py | 0 packages/modules/{ => devices}/http/bat.py | 12 ++-- packages/modules/{ => devices}/http/config.py | 0 .../modules/{ => devices}/http/counter.py | 12 ++-- packages/modules/{ => devices}/http/device.py | 8 +-- .../modules/{ => devices}/http/inverter.py | 12 ++-- .../{janitza => devices/huawei}/__init__.py | 0 packages/modules/{ => devices}/huawei/bat.py | 10 +-- .../modules/{ => devices}/huawei/config.py | 0 .../modules/{ => devices}/huawei/counter.py | 10 +-- .../modules/{ => devices}/huawei/device.py | 8 +-- .../modules/{ => devices}/huawei/inverter.py | 10 +-- .../{json => devices/janitza}/__init__.py | 0 .../modules/{ => devices}/janitza/config.py | 0 .../modules/{ => devices}/janitza/counter.py | 10 +-- .../modules/{ => devices}/janitza/device.py | 4 +- .../{kostal_piko => devices/json}/__init__.py | 0 packages/modules/{ => devices}/json/bat.py | 10 +-- packages/modules/{ => devices}/json/config.py | 0 .../modules/{ => devices}/json/counter.py | 10 +-- packages/modules/{ => devices}/json/device.py | 24 +++---- .../modules/{ => devices}/json/device_test.py | 6 +- .../modules/{ => devices}/json/inverter.py | 10 +-- .../{lg => devices/kostal_piko}/__init__.py | 0 .../{ => devices}/kostal_piko/config.py | 0 .../{ => devices}/kostal_piko/counter.py | 10 +-- .../{ => devices}/kostal_piko/device.py | 18 ++--- .../{ => devices}/kostal_piko/inverter.py | 9 +-- .../modules/devices/lg/JSON-Beispiele.txt | 69 +++++++++++++++++++ .../lg}/__init__.py | 0 packages/modules/{ => devices}/lg/bat.py | 10 +-- packages/modules/{ => devices}/lg/config.py | 0 packages/modules/{ => devices}/lg/counter.py | 10 +-- packages/modules/{ => devices}/lg/device.py | 4 +- packages/modules/{ => devices}/lg/inverter.py | 10 +-- packages/modules/{ => devices}/lg/lg_test.py | 4 +- .../openwb_bat_kit}/__init__.py | 0 .../{ => devices}/openwb_bat_kit/bat.py | 8 +-- .../{ => devices}/openwb_bat_kit/config.py | 0 .../{ => devices}/openwb_bat_kit/device.py | 4 +- .../openwb_evu_kit}/__init__.py | 0 .../{ => devices}/openwb_evu_kit/bat.py | 2 +- .../{ => devices}/openwb_evu_kit/config.py | 0 .../{ => devices}/openwb_evu_kit/counter.py | 6 +- .../{ => devices}/openwb_evu_kit/device.py | 14 ++-- .../{ => devices}/openwb_evu_kit/inverter.py | 2 +- .../openwb_flex}/__init__.py | 0 .../modules/{ => devices}/openwb_flex/bat.py | 12 ++-- .../{ => devices}/openwb_flex/config.py | 6 +- .../{ => devices}/openwb_flex/counter.py | 12 ++-- .../{ => devices}/openwb_flex/device.py | 8 +-- .../{ => devices}/openwb_flex/inverter.py | 12 ++-- .../{ => devices}/openwb_flex/versions.py | 0 .../openwb_pv_kit}/__init__.py | 0 .../{ => devices}/openwb_pv_kit/config.py | 0 .../{ => devices}/openwb_pv_kit/device.py | 4 +- .../{ => devices}/openwb_pv_kit/inverter.py | 8 +-- .../powerdog}/__init__.py | 0 .../modules/{ => devices}/powerdog/config.py | 0 .../modules/{ => devices}/powerdog/counter.py | 10 +-- .../modules/{ => devices}/powerdog/device.py | 6 +- .../{ => devices}/powerdog/inverter.py | 10 +-- .../{siemens => devices/saxpower}/__init__.py | 0 .../modules/{ => devices}/saxpower/bat.py | 10 +-- .../modules/{ => devices}/saxpower/config.py | 0 .../modules/{ => devices}/saxpower/device.py | 4 +- .../siemens}/__init__.py | 0 packages/modules/{ => devices}/siemens/bat.py | 10 +-- .../modules/{ => devices}/siemens/config.py | 0 .../modules/{ => devices}/siemens/counter.py | 10 +-- .../modules/{ => devices}/siemens/device.py | 4 +- .../modules/{ => devices}/siemens/inverter.py | 10 +-- .../siemens_sentron}/__init__.py | 0 .../{ => devices}/siemens_sentron/config.py | 0 .../{ => devices}/siemens_sentron/counter.py | 2 +- .../{ => devices}/siemens_sentron/device.py | 4 +- .../sma_shm}/__init__.py | 0 .../modules/{ => devices}/sma_shm/config.py | 0 .../modules/{ => devices}/sma_shm/counter.py | 4 +- .../{ => devices}/sma_shm/counter_test.py | 2 +- .../modules/{ => devices}/sma_shm/device.py | 10 +-- .../modules/{ => devices}/sma_shm/inverter.py | 4 +- .../sma_shm/speedwire_listener.py | 2 +- .../{ => devices}/sma_shm/speedwiredecoder.py | 0 .../modules/{ => devices}/sma_shm/utils.py | 7 +- .../sma_sunny_boy}/__init__.py | 0 .../{ => devices}/sma_sunny_boy/bat.py | 6 +- .../sma_sunny_boy/bat_smart_energy.py | 10 +-- .../{ => devices}/sma_sunny_boy/config.py | 2 +- .../{ => devices}/sma_sunny_boy/counter.py | 10 +-- .../{ => devices}/sma_sunny_boy/device.py | 14 ++-- .../sma_sunny_boy/inv_version.py | 0 .../{ => devices}/sma_sunny_boy/inverter.py | 4 +- .../sma_sunny_boy/sma_sunny_boy_test.py | 6 +- .../sma_sunny_island}/__init__.py | 0 .../{ => devices}/sma_sunny_island/bat.py | 6 +- .../{ => devices}/sma_sunny_island/config.py | 0 .../{ => devices}/sma_sunny_island/device.py | 4 +- .../sma_webbox}/__init__.py | 0 .../{ => devices}/sma_webbox/config.py | 0 .../{ => devices}/sma_webbox/device.py | 4 +- .../{ => devices}/sma_webbox/inverter.py | 6 +- .../solaredge}/__init__.py | 0 .../modules/{ => devices}/solaredge/bat.py | 12 ++-- .../modules/{ => devices}/solaredge/config.py | 4 +- .../{ => devices}/solaredge/counter.py | 10 +-- .../modules/{ => devices}/solaredge/device.py | 59 +++++----------- .../{ => devices}/solaredge/device_test.py | 10 +-- .../solaredge/external_inverter.py | 12 ++-- .../{ => devices}/solaredge/inverter.py | 10 +-- .../modules/{ => devices}/solaredge/meter.py | 0 .../{ => devices}/solaredge/meter_test.py | 2 +- .../modules/{ => devices}/solaredge/scale.py | 0 .../{solax => devices/solarmax}/__init__.py | 0 .../modules/{ => devices}/solarmax/config.py | 0 .../modules/{ => devices}/solarmax/device.py | 4 +- .../{ => devices}/solarmax/inverter.py | 10 +-- .../solax}/__init__.py | 0 packages/modules/{ => devices}/solax/bat.py | 10 +-- .../modules/{ => devices}/solax/config.py | 0 .../modules/{ => devices}/solax/counter.py | 6 +- .../modules/{ => devices}/solax/device.py | 8 +-- .../modules/{ => devices}/solax/inverter.py | 6 +- .../sonnenbatterie}/__init__.py | 0 .../{ => devices}/sonnenbatterie/bat.py | 10 +-- .../{ => devices}/sonnenbatterie/config.py | 0 .../{ => devices}/sonnenbatterie/counter.py | 12 ++-- .../{ => devices}/sonnenbatterie/device.py | 6 +- .../{ => devices}/sonnenbatterie/inverter.py | 12 ++-- .../{sungrow => devices/studer}/__init__.py | 0 packages/modules/{ => devices}/studer/bat.py | 6 +- .../modules/{ => devices}/studer/config.py | 0 .../modules/{ => devices}/studer/device.py | 6 +- .../modules/{ => devices}/studer/inverter.py | 6 +- .../{sunways => devices/sungrow}/__init__.py | 0 packages/modules/{ => devices}/sungrow/bat.py | 10 +-- .../modules/{ => devices}/sungrow/config.py | 0 .../modules/{ => devices}/sungrow/counter.py | 12 ++-- .../modules/{ => devices}/sungrow/device.py | 12 ++-- .../modules/{ => devices}/sungrow/inverter.py | 10 +-- .../modules/{ => devices}/sungrow/version.py | 0 .../{tesla => devices/sunways}/__init__.py | 0 .../modules/{ => devices}/sunways/config.py | 0 .../modules/{ => devices}/sunways/device.py | 4 +- .../modules/{ => devices}/sunways/inverter.py | 6 +- .../{victron => devices/tesla}/__init__.py | 0 packages/modules/{ => devices}/tesla/bat.py | 8 +-- .../modules/{ => devices}/tesla/config.py | 0 .../modules/{ => devices}/tesla/counter.py | 8 +-- .../modules/{ => devices}/tesla/device.py | 6 +- .../{ => devices}/tesla/http_client.py | 0 .../modules/{ => devices}/tesla/inverter.py | 8 +-- .../modules/{ => devices}/tesla/tesla_test.py | 6 +- packages/modules/devices/victron/__init__.py | 0 packages/modules/{ => devices}/victron/bat.py | 10 +-- .../modules/{ => devices}/victron/config.py | 0 .../modules/{ => devices}/victron/counter.py | 10 +-- .../modules/{ => devices}/victron/device.py | 8 +-- .../modules/{ => devices}/victron/inverter.py | 10 +-- packages/modules/evnotify/EVNotify.py | 39 ----------- packages/modules/lg/JSON-Beispiele.txt | 69 ------------------- packages/modules/vehicles/__init__.py | 0 .../{ => vehicles}/evnotify/EVNotify_test.py | 34 ++------- .../modules/vehicles/evnotify/__init__.py | 0 .../modules/{ => vehicles}/evnotify/api.py | 0 .../{ => vehicles}/evnotify/api_test.py | 2 +- packages/modules/vehicles/evnotify/config.py | 15 ++++ packages/modules/vehicles/evnotify/soc.py | 36 ++++++++++ packages/smarthome/test.py | 2 + runs/isss.py | 4 +- 243 files changed, 743 insertions(+), 787 deletions(-) rename packages/modules/{alpha_ess => chargepoints}/__init__.py (100%) rename packages/modules/{batterx => chargepoints/internal_openwb}/__init__.py (100%) rename packages/modules/{ => chargepoints}/internal_openwb/chargepoint_module.py (100%) rename packages/modules/{ => chargepoints}/internal_openwb/socket.py (100%) rename packages/modules/common/simcount/{_simcounter_state.py => simcounter_state.py} (100%) rename packages/modules/{byd => devices}/__init__.py (100%) rename packages/modules/{carlo_gavazzi => devices/alpha_ess}/__init__.py (100%) rename packages/modules/{ => devices}/alpha_ess/bat.py (84%) rename packages/modules/{ => devices}/alpha_ess/config.py (100%) rename packages/modules/{ => devices}/alpha_ess/counter.py (93%) rename packages/modules/{ => devices}/alpha_ess/device.py (94%) rename packages/modules/{ => devices}/alpha_ess/inverter.py (84%) rename packages/modules/{discovergy => devices/batterx}/__init__.py (100%) rename packages/modules/{ => devices}/batterx/bat.py (76%) rename packages/modules/{ => devices}/batterx/batterx_test.py (60%) rename packages/modules/{ => devices}/batterx/config.py (100%) rename packages/modules/{ => devices}/batterx/counter.py (85%) rename packages/modules/{ => devices}/batterx/device.py (92%) rename packages/modules/{ => devices}/batterx/inverter.py (75%) rename packages/modules/{enphase => devices/byd}/__init__.py (100%) rename packages/modules/{ => devices}/byd/bat.py (84%) rename packages/modules/{ => devices}/byd/byd_test.py (92%) rename packages/modules/{ => devices}/byd/byd_test_sample_home.html (90%) rename packages/modules/{ => devices}/byd/byd_test_sample_rundata.html (99%) rename packages/modules/{ => devices}/byd/config.py (92%) rename packages/modules/{ => devices}/byd/device.py (93%) rename packages/modules/{evnotify => devices/carlo_gavazzi}/__init__.py (100%) rename packages/modules/{ => devices}/carlo_gavazzi/config.py (100%) rename packages/modules/{ => devices}/carlo_gavazzi/counter.py (86%) rename packages/modules/{ => devices}/carlo_gavazzi/device.py (96%) rename packages/modules/{fronius => devices/discovergy}/__init__.py (100%) rename packages/modules/{ => devices}/discovergy/api.py (100%) rename packages/modules/{ => devices}/discovergy/api_test.py (98%) rename packages/modules/{ => devices}/discovergy/config.py (100%) rename packages/modules/{ => devices}/discovergy/counter.py (73%) rename packages/modules/{ => devices}/discovergy/device.py (94%) rename packages/modules/{ => devices}/discovergy/device_test.py (96%) rename packages/modules/{ => devices}/discovergy/inverter.py (83%) rename packages/modules/{ => devices}/discovergy/utils.py (65%) rename packages/modules/{ => devices}/e3dc/bat.py (96%) rename packages/modules/{ => devices}/e3dc/config.py (100%) rename packages/modules/{ => devices}/e3dc/counter.py (97%) rename packages/modules/{ => devices}/e3dc/device.py (92%) rename packages/modules/{ => devices}/e3dc/inverter.py (97%) rename packages/modules/{good_we => devices/enphase}/__init__.py (100%) rename packages/modules/{ => devices}/enphase/config.py (96%) rename packages/modules/{ => devices}/enphase/counter.py (97%) rename packages/modules/{ => devices}/enphase/device.py (84%) rename packages/modules/{ => devices}/enphase/inverter.py (96%) rename packages/modules/{http => devices/fronius}/__init__.py (100%) rename packages/modules/{ => devices}/fronius/bat.py (84%) rename packages/modules/{ => devices}/fronius/bat_test.py (95%) rename packages/modules/{ => devices}/fronius/config.py (100%) rename packages/modules/{ => devices}/fronius/counter_s0.py (80%) rename packages/modules/{ => devices}/fronius/counter_s0_test.py (95%) rename packages/modules/{ => devices}/fronius/counter_sm.py (93%) rename packages/modules/{ => devices}/fronius/counter_sm_test.py (99%) rename packages/modules/{ => devices}/fronius/device.py (93%) rename packages/modules/{ => devices}/fronius/inverter.py (83%) rename packages/modules/{ => devices}/fronius/inverter_test.py (96%) rename packages/modules/{ => devices}/fronius/meter_test.py (94%) rename packages/modules/{huawei => devices/good_we}/__init__.py (100%) rename packages/modules/{ => devices}/good_we/bat.py (91%) rename packages/modules/{ => devices}/good_we/config.py (94%) rename packages/modules/{ => devices}/good_we/counter.py (93%) rename packages/modules/{ => devices}/good_we/device.py (92%) rename packages/modules/{ => devices}/good_we/inverter.py (89%) rename packages/modules/{internal_openwb => devices/http}/__init__.py (100%) rename packages/modules/{ => devices}/http/api.py (100%) rename packages/modules/{ => devices}/http/bat.py (80%) rename packages/modules/{ => devices}/http/config.py (100%) rename packages/modules/{ => devices}/http/counter.py (82%) rename packages/modules/{ => devices}/http/device.py (94%) rename packages/modules/{ => devices}/http/inverter.py (78%) rename packages/modules/{janitza => devices/huawei}/__init__.py (100%) rename packages/modules/{ => devices}/huawei/bat.py (82%) rename packages/modules/{ => devices}/huawei/config.py (100%) rename packages/modules/{ => devices}/huawei/counter.py (82%) rename packages/modules/{ => devices}/huawei/device.py (95%) rename packages/modules/{ => devices}/huawei/inverter.py (80%) rename packages/modules/{json => devices/janitza}/__init__.py (100%) rename packages/modules/{ => devices}/janitza/config.py (100%) rename packages/modules/{ => devices}/janitza/counter.py (85%) rename packages/modules/{ => devices}/janitza/device.py (96%) rename packages/modules/{kostal_piko => devices/json}/__init__.py (100%) rename packages/modules/{ => devices}/json/bat.py (82%) rename packages/modules/{ => devices}/json/config.py (100%) rename packages/modules/{ => devices}/json/counter.py (81%) rename packages/modules/{ => devices}/json/device.py (80%) rename packages/modules/{ => devices}/json/device_test.py (88%) rename packages/modules/{ => devices}/json/inverter.py (79%) rename packages/modules/{lg => devices/kostal_piko}/__init__.py (100%) rename packages/modules/{ => devices}/kostal_piko/config.py (100%) rename packages/modules/{ => devices}/kostal_piko/counter.py (83%) rename packages/modules/{ => devices}/kostal_piko/device.py (89%) rename packages/modules/{ => devices}/kostal_piko/inverter.py (85%) create mode 100644 packages/modules/devices/lg/JSON-Beispiele.txt rename packages/modules/{openwb_bat_kit => devices/lg}/__init__.py (100%) rename packages/modules/{ => devices}/lg/bat.py (81%) rename packages/modules/{ => devices}/lg/config.py (100%) rename packages/modules/{ => devices}/lg/counter.py (77%) rename packages/modules/{ => devices}/lg/device.py (97%) rename packages/modules/{ => devices}/lg/inverter.py (76%) rename packages/modules/{ => devices}/lg/lg_test.py (97%) rename packages/modules/{openwb_evu_kit => devices/openwb_bat_kit}/__init__.py (100%) rename packages/modules/{ => devices}/openwb_bat_kit/bat.py (78%) rename packages/modules/{ => devices}/openwb_bat_kit/config.py (100%) rename packages/modules/{ => devices}/openwb_bat_kit/device.py (96%) rename packages/modules/{openwb_flex => devices/openwb_evu_kit}/__init__.py (100%) rename packages/modules/{ => devices}/openwb_evu_kit/bat.py (72%) rename packages/modules/{ => devices}/openwb_evu_kit/config.py (100%) rename packages/modules/{ => devices}/openwb_evu_kit/counter.py (83%) rename packages/modules/{ => devices}/openwb_evu_kit/device.py (89%) rename packages/modules/{ => devices}/openwb_evu_kit/inverter.py (71%) rename packages/modules/{openwb_pv_kit => devices/openwb_flex}/__init__.py (100%) rename packages/modules/{ => devices}/openwb_flex/bat.py (82%) rename packages/modules/{ => devices}/openwb_flex/config.py (93%) rename packages/modules/{ => devices}/openwb_flex/counter.py (85%) rename packages/modules/{ => devices}/openwb_flex/device.py (93%) rename packages/modules/{ => devices}/openwb_flex/inverter.py (81%) rename packages/modules/{ => devices}/openwb_flex/versions.py (100%) rename packages/modules/{powerdog => devices/openwb_pv_kit}/__init__.py (100%) rename packages/modules/{ => devices}/openwb_pv_kit/config.py (100%) rename packages/modules/{ => devices}/openwb_pv_kit/device.py (96%) rename packages/modules/{ => devices}/openwb_pv_kit/inverter.py (77%) rename packages/modules/{saxpower => devices/powerdog}/__init__.py (100%) rename packages/modules/{ => devices}/powerdog/config.py (100%) rename packages/modules/{ => devices}/powerdog/counter.py (82%) rename packages/modules/{ => devices}/powerdog/device.py (95%) rename packages/modules/{ => devices}/powerdog/inverter.py (80%) rename packages/modules/{siemens => devices/saxpower}/__init__.py (100%) rename packages/modules/{ => devices}/saxpower/bat.py (81%) rename packages/modules/{ => devices}/saxpower/config.py (100%) rename packages/modules/{ => devices}/saxpower/device.py (96%) rename packages/modules/{siemens_sentron => devices/siemens}/__init__.py (100%) rename packages/modules/{ => devices}/siemens/bat.py (81%) rename packages/modules/{ => devices}/siemens/config.py (100%) rename packages/modules/{ => devices}/siemens/counter.py (79%) rename packages/modules/{ => devices}/siemens/device.py (95%) rename packages/modules/{ => devices}/siemens/inverter.py (79%) rename packages/modules/{sma_shm => devices/siemens_sentron}/__init__.py (100%) rename packages/modules/{ => devices}/siemens_sentron/config.py (100%) rename packages/modules/{ => devices}/siemens_sentron/counter.py (96%) rename packages/modules/{ => devices}/siemens_sentron/device.py (95%) rename packages/modules/{sma_sunny_boy => devices/sma_shm}/__init__.py (100%) rename packages/modules/{ => devices}/sma_shm/config.py (100%) rename packages/modules/{ => devices}/sma_shm/counter.py (92%) rename packages/modules/{ => devices}/sma_shm/counter_test.py (97%) rename packages/modules/{ => devices}/sma_shm/device.py (87%) rename packages/modules/{ => devices}/sma_shm/inverter.py (82%) rename packages/modules/{ => devices}/sma_shm/speedwire_listener.py (95%) rename packages/modules/{ => devices}/sma_shm/speedwiredecoder.py (100%) rename packages/modules/{ => devices}/sma_shm/utils.py (83%) rename packages/modules/{sma_sunny_island => devices/sma_sunny_boy}/__init__.py (100%) rename packages/modules/{ => devices}/sma_sunny_boy/bat.py (90%) rename packages/modules/{ => devices}/sma_sunny_boy/bat_smart_energy.py (84%) rename packages/modules/{ => devices}/sma_sunny_boy/config.py (97%) rename packages/modules/{ => devices}/sma_sunny_boy/counter.py (81%) rename packages/modules/{ => devices}/sma_sunny_boy/device.py (93%) rename packages/modules/{ => devices}/sma_sunny_boy/inv_version.py (100%) rename packages/modules/{ => devices}/sma_sunny_boy/inverter.py (95%) rename packages/modules/{ => devices}/sma_sunny_boy/sma_sunny_boy_test.py (91%) rename packages/modules/{sma_webbox => devices/sma_sunny_island}/__init__.py (100%) rename packages/modules/{ => devices}/sma_sunny_island/bat.py (89%) rename packages/modules/{ => devices}/sma_sunny_island/config.py (100%) rename packages/modules/{ => devices}/sma_sunny_island/device.py (95%) rename packages/modules/{solaredge => devices/sma_webbox}/__init__.py (100%) rename packages/modules/{ => devices}/sma_webbox/config.py (100%) rename packages/modules/{ => devices}/sma_webbox/device.py (94%) rename packages/modules/{ => devices}/sma_webbox/inverter.py (88%) rename packages/modules/{solarmax => devices/solaredge}/__init__.py (100%) rename packages/modules/{ => devices}/solaredge/bat.py (84%) rename packages/modules/{ => devices}/solaredge/config.py (93%) rename packages/modules/{ => devices}/solaredge/counter.py (88%) rename packages/modules/{ => devices}/solaredge/device.py (80%) rename packages/modules/{ => devices}/solaredge/device_test.py (86%) rename packages/modules/{ => devices}/solaredge/external_inverter.py (83%) rename packages/modules/{ => devices}/solaredge/inverter.py (88%) rename packages/modules/{ => devices}/solaredge/meter.py (100%) rename packages/modules/{ => devices}/solaredge/meter_test.py (93%) rename packages/modules/{ => devices}/solaredge/scale.py (100%) rename packages/modules/{solax => devices/solarmax}/__init__.py (100%) rename packages/modules/{ => devices}/solarmax/config.py (100%) rename packages/modules/{ => devices}/solarmax/device.py (96%) rename packages/modules/{ => devices}/solarmax/inverter.py (81%) rename packages/modules/{sonnenbatterie => devices/solax}/__init__.py (100%) rename packages/modules/{ => devices}/solax/bat.py (82%) rename packages/modules/{ => devices}/solax/config.py (100%) rename packages/modules/{ => devices}/solax/counter.py (92%) rename packages/modules/{ => devices}/solax/device.py (94%) rename packages/modules/{ => devices}/solax/inverter.py (90%) rename packages/modules/{studer => devices/sonnenbatterie}/__init__.py (100%) rename packages/modules/{ => devices}/sonnenbatterie/bat.py (93%) rename packages/modules/{ => devices}/sonnenbatterie/config.py (100%) rename packages/modules/{ => devices}/sonnenbatterie/counter.py (91%) rename packages/modules/{ => devices}/sonnenbatterie/device.py (93%) rename packages/modules/{ => devices}/sonnenbatterie/inverter.py (91%) rename packages/modules/{sungrow => devices/studer}/__init__.py (100%) rename packages/modules/{ => devices}/studer/bat.py (90%) rename packages/modules/{ => devices}/studer/config.py (100%) rename packages/modules/{ => devices}/studer/device.py (94%) rename packages/modules/{ => devices}/studer/inverter.py (93%) rename packages/modules/{sunways => devices/sungrow}/__init__.py (100%) rename packages/modules/{ => devices}/sungrow/bat.py (83%) rename packages/modules/{ => devices}/sungrow/config.py (100%) rename packages/modules/{ => devices}/sungrow/counter.py (88%) rename packages/modules/{ => devices}/sungrow/device.py (92%) rename packages/modules/{ => devices}/sungrow/inverter.py (82%) rename packages/modules/{ => devices}/sungrow/version.py (100%) rename packages/modules/{tesla => devices/sunways}/__init__.py (100%) rename packages/modules/{ => devices}/sunways/config.py (100%) rename packages/modules/{ => devices}/sunways/device.py (96%) rename packages/modules/{ => devices}/sunways/inverter.py (90%) rename packages/modules/{victron => devices/tesla}/__init__.py (100%) rename packages/modules/{ => devices}/tesla/bat.py (82%) rename packages/modules/{ => devices}/tesla/config.py (100%) rename packages/modules/{ => devices}/tesla/counter.py (89%) rename packages/modules/{ => devices}/tesla/device.py (96%) rename packages/modules/{ => devices}/tesla/http_client.py (100%) rename packages/modules/{ => devices}/tesla/inverter.py (80%) rename packages/modules/{ => devices}/tesla/tesla_test.py (97%) create mode 100644 packages/modules/devices/victron/__init__.py rename packages/modules/{ => devices}/victron/bat.py (82%) rename packages/modules/{ => devices}/victron/config.py (100%) rename packages/modules/{ => devices}/victron/counter.py (87%) rename packages/modules/{ => devices}/victron/device.py (94%) rename packages/modules/{ => devices}/victron/inverter.py (87%) delete mode 100644 packages/modules/evnotify/EVNotify.py delete mode 100644 packages/modules/lg/JSON-Beispiele.txt create mode 100644 packages/modules/vehicles/__init__.py rename packages/modules/{ => vehicles}/evnotify/EVNotify_test.py (55%) create mode 100644 packages/modules/vehicles/evnotify/__init__.py rename packages/modules/{ => vehicles}/evnotify/api.py (100%) rename packages/modules/{ => vehicles}/evnotify/api_test.py (97%) create mode 100644 packages/modules/vehicles/evnotify/config.py create mode 100644 packages/modules/vehicles/evnotify/soc.py create mode 100644 packages/smarthome/test.py diff --git a/packages/modules/alpha_ess/__init__.py b/packages/modules/chargepoints/__init__.py similarity index 100% rename from packages/modules/alpha_ess/__init__.py rename to packages/modules/chargepoints/__init__.py diff --git a/packages/modules/batterx/__init__.py b/packages/modules/chargepoints/internal_openwb/__init__.py similarity index 100% rename from packages/modules/batterx/__init__.py rename to packages/modules/chargepoints/internal_openwb/__init__.py diff --git a/packages/modules/internal_openwb/chargepoint_module.py b/packages/modules/chargepoints/internal_openwb/chargepoint_module.py similarity index 100% rename from packages/modules/internal_openwb/chargepoint_module.py rename to packages/modules/chargepoints/internal_openwb/chargepoint_module.py diff --git a/packages/modules/internal_openwb/socket.py b/packages/modules/chargepoints/internal_openwb/socket.py similarity index 100% rename from packages/modules/internal_openwb/socket.py rename to packages/modules/chargepoints/internal_openwb/socket.py diff --git a/packages/modules/common/simcount/__init__.py b/packages/modules/common/simcount/__init__.py index 49c2dda8f8..add2e1ac5d 100644 --- a/packages/modules/common/simcount/__init__.py +++ b/packages/modules/common/simcount/__init__.py @@ -1,3 +1,3 @@ from modules.common.simcount._simcount import sim_count from modules.common.simcount._simcounter import SimCounter -from modules.common.simcount._simcounter_state import SimCounterState +from modules.common.simcount.simcounter_state import SimCounterState diff --git a/packages/modules/common/simcount/_simcount.py b/packages/modules/common/simcount/_simcount.py index 6331abeff2..6ff825e626 100644 --- a/packages/modules/common/simcount/_simcount.py +++ b/packages/modules/common/simcount/_simcount.py @@ -6,7 +6,7 @@ from modules.common.fault_state import exceptions_to_fault_state from modules.common.simcount._calculate import calculate_import_export -from modules.common.simcount._simcounter_state import SimCounterState +from modules.common.simcount.simcounter_state import SimCounterState from modules.common.simcount._simcounter_store import get_sim_counter_store log = logging.getLogger(__name__) diff --git a/packages/modules/common/simcount/_simcounter.py b/packages/modules/common/simcount/_simcounter.py index c0a2b69767..0e65dec191 100644 --- a/packages/modules/common/simcount/_simcounter.py +++ b/packages/modules/common/simcount/_simcounter.py @@ -1,7 +1,7 @@ from typing import Tuple, Optional from modules.common.simcount._simcount import sim_count -from modules.common.simcount._simcounter_state import SimCounterState +from modules.common.simcount.simcounter_state import SimCounterState class SimCounter: diff --git a/packages/modules/common/simcount/_simcounter_store.py b/packages/modules/common/simcount/_simcounter_store.py index 182a666ce7..03563c35d8 100644 --- a/packages/modules/common/simcount/_simcounter_store.py +++ b/packages/modules/common/simcount/_simcounter_store.py @@ -7,7 +7,7 @@ from paho.mqtt.client import Client as MqttClient, MQTTMessage from helpermodules import pub, compatibility -from modules.common.simcount._simcounter_state import SimCounterState +from modules.common.simcount.simcounter_state import SimCounterState from modules.common.store import ramdisk_write, ramdisk_read_float from modules.common.store.ramdisk.io import RamdiskReadError diff --git a/packages/modules/common/simcount/_simcounter_state.py b/packages/modules/common/simcount/simcounter_state.py similarity index 100% rename from packages/modules/common/simcount/_simcounter_state.py rename to packages/modules/common/simcount/simcounter_state.py diff --git a/packages/modules/conftest.py b/packages/modules/conftest.py index 865b93fc4b..2322a08961 100644 --- a/packages/modules/conftest.py +++ b/packages/modules/conftest.py @@ -6,6 +6,9 @@ from modules.common import simcount sys.modules['pymodbus'] = type(sys)('pymodbus') +sys.modules['aiohttp'] = type(sys)('aiohttp') +sys.modules['lxml'] = type(sys)('lxml') +sys.modules['lxml.html'] = type(sys)('lxml.html') module = type(sys)('pymodbus.client.sync') module.ModbusSerialClient = Mock() diff --git a/packages/modules/byd/__init__.py b/packages/modules/devices/__init__.py similarity index 100% rename from packages/modules/byd/__init__.py rename to packages/modules/devices/__init__.py diff --git a/packages/modules/carlo_gavazzi/__init__.py b/packages/modules/devices/alpha_ess/__init__.py similarity index 100% rename from packages/modules/carlo_gavazzi/__init__.py rename to packages/modules/devices/alpha_ess/__init__.py diff --git a/packages/modules/alpha_ess/bat.py b/packages/modules/devices/alpha_ess/bat.py similarity index 84% rename from packages/modules/alpha_ess/bat.py rename to packages/modules/devices/alpha_ess/bat.py index ce22942a8c..3f15ffdac2 100644 --- a/packages/modules/alpha_ess/bat.py +++ b/packages/modules/devices/alpha_ess/bat.py @@ -4,7 +4,7 @@ from typing import Dict, Union from dataclass_utils import dataclass_from_dict -from modules.alpha_ess.config import AlphaEssBatSetup, AlphaEssConfiguration +from modules.devices.alpha_ess.config import AlphaEssBatSetup, AlphaEssConfiguration from modules.common import modbus from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -24,8 +24,8 @@ def __init__(self, device_id: int, self.__device_id = device_id self.component_config = dataclass_from_dict(AlphaEssBatSetup, component_config) self.__tcp_client = tcp_client - self.__sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") - self.__store = get_bat_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) def update(self, unit_id: int) -> None: @@ -45,14 +45,14 @@ def update(self, unit_id: int) -> None: soc_reg = self.__tcp_client.read_holding_registers(0x0102, ModbusDataType.INT_16, unit=unit_id) soc = int(soc_reg * 0.1) - imported, exported = self.__sim_counter.sim_count(power) + imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) - self.__store.set(bat_state) + self.store.set(bat_state) component_descriptor = ComponentDescriptor(configuration_factory=AlphaEssBatSetup) diff --git a/packages/modules/alpha_ess/config.py b/packages/modules/devices/alpha_ess/config.py similarity index 100% rename from packages/modules/alpha_ess/config.py rename to packages/modules/devices/alpha_ess/config.py diff --git a/packages/modules/alpha_ess/counter.py b/packages/modules/devices/alpha_ess/counter.py similarity index 93% rename from packages/modules/alpha_ess/counter.py rename to packages/modules/devices/alpha_ess/counter.py index 6801741d6d..8779197d52 100644 --- a/packages/modules/alpha_ess/counter.py +++ b/packages/modules/devices/alpha_ess/counter.py @@ -3,7 +3,7 @@ from typing import Callable, Dict, Union from dataclass_utils import dataclass_from_dict -from modules.alpha_ess.config import AlphaEssConfiguration, AlphaEssCounterSetup +from modules.devices.alpha_ess.config import AlphaEssConfiguration, AlphaEssCounterSetup from modules.common import modbus from modules.common.component_state import CounterState from modules.common.component_type import ComponentDescriptor @@ -20,7 +20,7 @@ def __init__(self, device_config: AlphaEssConfiguration) -> None: self.component_config = dataclass_from_dict(AlphaEssCounterSetup, component_config) self.__tcp_client = tcp_client - self.__store = get_counter_value_store(self.component_config.id) + self.store = get_counter_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) self.__device_config = device_config @@ -28,7 +28,7 @@ def update(self, unit_id: int): time.sleep(0.1) factory_method = self.__get_values_factory() counter_state = factory_method(unit_id) - self.__store.set(counter_state) + self.store.set(counter_state) def __get_values_factory(self,) -> Callable[[int], CounterState]: if self.__device_config.source == 0 and self.__device_config.version == 0: diff --git a/packages/modules/alpha_ess/device.py b/packages/modules/devices/alpha_ess/device.py similarity index 94% rename from packages/modules/alpha_ess/device.py rename to packages/modules/devices/alpha_ess/device.py index 8a2c89346a..1b9a878ec0 100644 --- a/packages/modules/alpha_ess/device.py +++ b/packages/modules/devices/alpha_ess/device.py @@ -2,15 +2,15 @@ import logging from typing import Dict, Union, Optional, List -from modules.alpha_ess.config import AlphaEss, AlphaEssBatSetup, AlphaEssCounterSetup, AlphaEssInverterSetup +from modules.devices.alpha_ess.config import AlphaEss, AlphaEssBatSetup, AlphaEssCounterSetup, AlphaEssInverterSetup from dataclass_utils import dataclass_from_dict from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus from modules.common.abstract_device import AbstractDevice, DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.alpha_ess import bat -from modules.alpha_ess import counter -from modules.alpha_ess import inverter +from modules.devices.alpha_ess import bat +from modules.devices.alpha_ess import counter +from modules.devices.alpha_ess import inverter log = logging.getLogger(__name__) diff --git a/packages/modules/alpha_ess/inverter.py b/packages/modules/devices/alpha_ess/inverter.py similarity index 84% rename from packages/modules/alpha_ess/inverter.py rename to packages/modules/devices/alpha_ess/inverter.py index 910f9b2037..0e55290347 100644 --- a/packages/modules/alpha_ess/inverter.py +++ b/packages/modules/devices/alpha_ess/inverter.py @@ -2,7 +2,7 @@ from typing import Dict, Union from dataclass_utils import dataclass_from_dict -from modules.alpha_ess.config import AlphaEssConfiguration, AlphaEssInverterSetup +from modules.devices.alpha_ess.config import AlphaEssConfiguration, AlphaEssInverterSetup from modules.common import modbus from modules.common.component_state import InverterState from modules.common.component_type import ComponentDescriptor @@ -20,8 +20,8 @@ def __init__(self, device_id: int, self.__device_id = device_id self.component_config = dataclass_from_dict(AlphaEssInverterSetup, component_config) self.__tcp_client = tcp_client - self.__sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") - self.__store = get_inverter_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + self.store = get_inverter_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) self.__device_config = device_config @@ -29,12 +29,12 @@ def update(self, unit_id: int) -> None: reg_p = self.__version_factory() power = self.__get_power(unit_id, reg_p) - _, exported = self.__sim_counter.sim_count(power) + _, exported = self.sim_counter.sim_count(power) inverter_state = InverterState( power=power, exported=exported ) - self.__store.set(inverter_state) + self.store.set(inverter_state) def __version_factory(self) -> int: if self.__device_config.source == 0 and self.__device_config.version == 0: diff --git a/packages/modules/discovergy/__init__.py b/packages/modules/devices/batterx/__init__.py similarity index 100% rename from packages/modules/discovergy/__init__.py rename to packages/modules/devices/batterx/__init__.py diff --git a/packages/modules/batterx/bat.py b/packages/modules/devices/batterx/bat.py similarity index 76% rename from packages/modules/batterx/bat.py rename to packages/modules/devices/batterx/bat.py index 6d85c691f4..a2c02974cb 100644 --- a/packages/modules/batterx/bat.py +++ b/packages/modules/devices/batterx/bat.py @@ -2,7 +2,7 @@ from typing import Dict, Union from dataclass_utils import dataclass_from_dict -from modules.batterx.config import BatterXBatSetup +from modules.devices.batterx.config import BatterXBatSetup from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo @@ -14,21 +14,21 @@ class BatterXBat: def __init__(self, device_id: int, component_config: Union[Dict, BatterXBatSetup]) -> None: self.__device_id = device_id self.component_config = dataclass_from_dict(BatterXBatSetup, component_config) - self.__sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") - self.__store = get_bat_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) def update(self, resp: Dict) -> None: power = resp["1121"]["1"] soc = resp["1074"]["1"] - imported, exported = self.__sim_counter.sim_count(power) + imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) - self.__store.set(bat_state) + self.store.set(bat_state) component_descriptor = ComponentDescriptor(configuration_factory=BatterXBatSetup) diff --git a/packages/modules/batterx/batterx_test.py b/packages/modules/devices/batterx/batterx_test.py similarity index 60% rename from packages/modules/batterx/batterx_test.py rename to packages/modules/devices/batterx/batterx_test.py index 58078b2934..026c258b73 100644 --- a/packages/modules/batterx/batterx_test.py +++ b/packages/modules/devices/batterx/batterx_test.py @@ -1,42 +1,37 @@ from unittest.mock import Mock -import pytest import requests_mock from modules.common.component_state import BatState, CounterState, InverterState -from modules.batterx import bat, counter, device, inverter +from modules.devices.batterx import bat, counter, device, inverter +from modules.devices.batterx.config import (BatterX, BatterXBatSetup, BatterXConfiguration, BatterXCounterSetup, + BatterXInverterSetup) -class TestBatterXDevice: - @pytest.fixture(autouse=True) - def setup(self, monkeypatch, requests_mock: requests_mock.mock): - self.mock_bat_value_store = Mock() - self.mock_counter_value_store = Mock() - self.mock_inverter_value_store = Mock() - self.mock_simcount = Mock() - monkeypatch.setattr(bat, 'get_bat_value_store', Mock(return_value=self.mock_bat_value_store)) - monkeypatch.setattr(counter, 'get_counter_value_store', Mock(return_value=self.mock_counter_value_store)) - monkeypatch.setattr(inverter, 'get_inverter_value_store', Mock(return_value=self.mock_inverter_value_store)) - requests_mock.get("http://1.1.1.1/api.php?get=currentstate", json=SAMPLE) +def test_batterx(monkeypatch, requests_mock: requests_mock.mock): + # setup + mock_bat_value_store = Mock() + mock_counter_value_store = Mock() + mock_inverter_value_store = Mock() + monkeypatch.setattr(bat, 'get_bat_value_store', Mock(return_value=mock_bat_value_store)) + monkeypatch.setattr(counter, 'get_counter_value_store', Mock(return_value=mock_counter_value_store)) + monkeypatch.setattr(inverter, 'get_inverter_value_store', Mock(return_value=mock_inverter_value_store)) + requests_mock.get("http://1.1.1.1/api.php?get=currentstate", json=SAMPLE) - def test_read(self): - # setup - device_config = device.BatterX() - device_config.configuration.ip_address = "1.1.1.1" - dev = device.Device(device_config) - dev = device._add_component(dev, "inverter", 1) - dev = device._add_component(dev, "counter", None) - dev = device._add_component(dev, "bat", None) + dev = device.Device(BatterX(configuration=BatterXConfiguration(ip_address="1.1.1.1"))) + dev.add_component(BatterXBatSetup(id=2)) + dev.add_component(BatterXCounterSetup(id=0)) + dev.add_component(BatterXInverterSetup(id=1)) - # execution - dev.update() + # execution + dev.update() - # evaluation - assert self.mock_counter_value_store.set.call_count == 1 - assert vars(self.mock_bat_value_store.set.call_args[0][0]) == vars(SAMPLE_BAT_STATE) - assert vars(self.mock_counter_value_store.set.call_args[0][0]) == vars(SAMPLE_COUNTER_STATE) - assert vars(self.mock_inverter_value_store.set.call_args[0][0]) == vars(SAMPLE_INVERTER_STATE) + # evaluation + assert mock_counter_value_store.set.call_count == 1 + assert vars(mock_bat_value_store.set.call_args[0][0]) == vars(SAMPLE_BAT_STATE) + assert vars(mock_counter_value_store.set.call_args[0][0]) == vars(SAMPLE_COUNTER_STATE) + assert vars(mock_inverter_value_store.set.call_args[0][0]) == vars(SAMPLE_INVERTER_STATE) SAMPLE_BAT_STATE = BatState( diff --git a/packages/modules/batterx/config.py b/packages/modules/devices/batterx/config.py similarity index 100% rename from packages/modules/batterx/config.py rename to packages/modules/devices/batterx/config.py diff --git a/packages/modules/batterx/counter.py b/packages/modules/devices/batterx/counter.py similarity index 85% rename from packages/modules/batterx/counter.py rename to packages/modules/devices/batterx/counter.py index aa9e0d1adc..e9a5c6de8e 100644 --- a/packages/modules/batterx/counter.py +++ b/packages/modules/devices/batterx/counter.py @@ -3,7 +3,7 @@ from typing import Dict, List, Union from dataclass_utils import dataclass_from_dict -from modules.batterx.config import BatterXCounterSetup +from modules.devices.batterx.config import BatterXCounterSetup from modules.common.component_state import CounterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo @@ -17,8 +17,8 @@ class BatterXCounter: def __init__(self, device_id: int, component_config: Union[Dict, BatterXCounterSetup]) -> None: self.__device_id = device_id self.component_config = dataclass_from_dict(BatterXCounterSetup, component_config) - self.__sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") - self.__store = get_counter_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + self.store = get_counter_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) def update(self, resp: Dict) -> None: @@ -33,7 +33,7 @@ def update(self, resp: Dict) -> None: log.debug( "Powerfaktor sollte laut Doku enthalten sein, ID 2881 kann aber nicht ermittelt werden.") power_factors = None - imported, exported = self.__sim_counter.sim_count(power) + imported, exported = self.sim_counter.sim_count(power) counter_state = CounterState( imported=imported, @@ -46,7 +46,7 @@ def update(self, resp: Dict) -> None: ) if power_factors: counter_state.power_factors = power_factors - self.__store.set(counter_state) + self.store.set(counter_state) def __parse_list_values(self, resp_json: Dict, id: int, factor: int = 1) -> List[float]: diff --git a/packages/modules/batterx/device.py b/packages/modules/devices/batterx/device.py similarity index 92% rename from packages/modules/batterx/device.py rename to packages/modules/devices/batterx/device.py index 99665c15f3..178daa7d05 100644 --- a/packages/modules/batterx/device.py +++ b/packages/modules/devices/batterx/device.py @@ -6,10 +6,10 @@ from helpermodules.cli import run_using_positional_cli_args from modules.common.abstract_device import AbstractDevice, DeviceDescriptor from modules.common.component_context import MultiComponentUpdateContext -from modules.batterx import bat -from modules.batterx import counter -from modules.batterx import inverter -from modules.batterx.config import BatterX, BatterXBatSetup, BatterXCounterSetup, BatterXInverterSetup +from modules.devices.batterx import bat +from modules.devices.batterx import counter +from modules.devices.batterx import inverter +from modules.devices.batterx.config import BatterX, BatterXBatSetup, BatterXCounterSetup, BatterXInverterSetup from modules.common import req log = logging.getLogger(__name__) @@ -44,7 +44,7 @@ def add_component(self, component_config: Union[Dict, component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ component_type].component_descriptor.configuration_factory, component_config) if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.type)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( + self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( self.device_config.id, component_config)) else: raise Exception( diff --git a/packages/modules/batterx/inverter.py b/packages/modules/devices/batterx/inverter.py similarity index 75% rename from packages/modules/batterx/inverter.py rename to packages/modules/devices/batterx/inverter.py index 1ce4793929..d889900741 100644 --- a/packages/modules/batterx/inverter.py +++ b/packages/modules/devices/batterx/inverter.py @@ -2,7 +2,7 @@ from typing import Dict, Union from dataclass_utils import dataclass_from_dict -from modules.batterx.config import BatterXInverterSetup +from modules.devices.batterx.config import BatterXInverterSetup from modules.common.component_state import InverterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo @@ -14,20 +14,20 @@ class BatterXInverter: def __init__(self, device_id: int, component_config: Union[Dict, BatterXInverterSetup]) -> None: self.__device_id = device_id self.component_config = dataclass_from_dict(BatterXInverterSetup, component_config) - self.__sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") - self.__store = get_inverter_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + self.store = get_inverter_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) def update(self, resp: Dict) -> None: power = resp["1634"]["0"] * -1 - _, exported = self.__sim_counter.sim_count(power) + _, exported = self.sim_counter.sim_count(power) inverter_state = InverterState( power=power, exported=exported ) - self.__store.set(inverter_state) + self.store.set(inverter_state) component_descriptor = ComponentDescriptor(configuration_factory=BatterXInverterSetup) diff --git a/packages/modules/enphase/__init__.py b/packages/modules/devices/byd/__init__.py similarity index 100% rename from packages/modules/enphase/__init__.py rename to packages/modules/devices/byd/__init__.py diff --git a/packages/modules/byd/bat.py b/packages/modules/devices/byd/bat.py similarity index 84% rename from packages/modules/byd/bat.py rename to packages/modules/devices/byd/bat.py index 58a6740b05..5baa1b3360 100644 --- a/packages/modules/byd/bat.py +++ b/packages/modules/devices/byd/bat.py @@ -4,7 +4,7 @@ from typing import Dict, List, Union, Tuple from dataclass_utils import dataclass_from_dict -from modules.byd.config import BYDBatSetup +from modules.devices.byd.config import BYDBatSetup from modules.common import req from modules.common.component_state import BatState from modules.common.component_type import ComponentDescriptor @@ -21,21 +21,21 @@ def __init__(self, device_config) -> None: self.__device_config = device_config self.component_config = dataclass_from_dict(BYDBatSetup, component_config) - self.__sim_counter = SimCounter(self.__device_config.id, self.component_config.id, prefix="speicher") - self.__store = get_bat_value_store(self.component_config.id) + self.sim_counter = SimCounter(self.__device_config.id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) self.component_info = ComponentInfo.from_component_config(self.component_config) def update(self) -> None: power, soc = self.get_values() - imported, exported = self.__sim_counter.sim_count(power) + imported, exported = self.sim_counter.sim_count(power) bat_state = BatState( power=power, soc=soc, imported=imported, exported=exported ) - self.__store.set(bat_state) + self.store.set(bat_state) def get_values(self) -> Tuple[float, float]: '''BYD Speicher bieten zwei HTML-Seiten, auf denen Informationen abgegriffen werden können: @@ -44,7 +44,7 @@ def get_values(self) -> Tuple[float, float]: ''' resp = req.get_http_session().get( 'http://' + self.__device_config.configuration.ip_address + '/asp/Home.asp', - auth=(self.__device_config.configuration.username, self.__device_config.configuration.password)) + auth=(self.__device_config.configuration.user, self.__device_config.configuration.password)) return BydParser.parse(resp.text) diff --git a/packages/modules/byd/byd_test.py b/packages/modules/devices/byd/byd_test.py similarity index 92% rename from packages/modules/byd/byd_test.py rename to packages/modules/devices/byd/byd_test.py index dd1596dd23..a4205559c5 100644 --- a/packages/modules/byd/byd_test.py +++ b/packages/modules/devices/byd/byd_test.py @@ -1,6 +1,6 @@ from pathlib import Path -from modules.byd import bat +from modules.devices.byd import bat def test_byd_rundata(): diff --git a/packages/modules/byd/byd_test_sample_home.html b/packages/modules/devices/byd/byd_test_sample_home.html similarity index 90% rename from packages/modules/byd/byd_test_sample_home.html rename to packages/modules/devices/byd/byd_test_sample_home.html index e11050607c..b7afdd9923 100644 --- a/packages/modules/byd/byd_test_sample_home.html +++ b/packages/modules/devices/byd/byd_test_sample_home.html @@ -1,5 +1,4 @@ - - + Home @@ -12,22 +11,22 @@ window.setInterval(loadXMLDoc, 1000); - function loadXMLDoc() - { - var xmlhttp; + function loadXMLDoc() + { + var xmlhttp; var d1 = []; var str = new Array(); - if (window.XMLHttpRequest) - {// code for IE7+, Firefox, Chrome, Opera, Safari - xmlhttp=new XMLHttpRequest(); - } - else - {// code for IE6, IE5 - xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); - } - xmlhttp.onreadystatechange=function() - { - if (xmlhttp.readyState==4 && xmlhttp.status==200) + if (window.XMLHttpRequest) + {// code for IE7+, Firefox, Chrome, Opera, Safari + xmlhttp=new XMLHttpRequest(); + } + else + {// code for IE6, IE5 + xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); + } + xmlhttp.onreadystatechange=function() + { + if (xmlhttp.readyState==4 && xmlhttp.status==200) { strs = xmlhttp.responseText.split(","); for (i=0; i @@ -1191,6 +1191,11 @@